2010-11-21 3 views
39

전달되는 정수를 증가시키는 Action을 만들 수있는 함수를 만들려고합니다. 그러나 첫 번째 시도에서 "ref를 사용할 수 없습니다. out 매개 변수를 익명 메서드 본문 내에서 사용 "합니다. 컴파일러는이를 좋아하지 않는 이유C# 익명 메서드 본문 내에서 ref 또는 out 매개 변수를 사용할 수 없습니다.

public static class IntEx { 
    public static Action CreateIncrementer(ref int reference) { 
     return() => { 
      reference += 1; 
     }; 
    } 
} 

은 이해하지만, 그럼에도 불구하고 나는 정수로 지적 할 수있는 좋은 증가 기 공장을 제공하는 우아한 방법을 가지고 싶습니다. 내가이 일을보고 있어요 유일한 방법은 같은 것입니다 다음

public static class IntEx { 
    public static Action CreateIncrementer(Func<int> getter, Action<int> setter) { 
     return() => setter(getter() + 1); 
    } 
} 

그러나 사용하는 호출자의 고통의 더 물론

; 호출자가 참조를 전달하는 대신 두 개의 람다를 작성해야합니다. 이 기능을 제공하기위한 더 좋은 방법이 있습니까? 아니면 그냥 2-λ 옵션으로 살 필요가 있습니까?

+2

간단한 예입니까? x ++를 사용하지 않는 이유는 무엇입니까? 이 클래스의 상태를 다른 클래스가 증가시키는 이유는 무엇입니까? – Gishu

+2

@ Gishu 예 이것은 간단한 예입니다. 더 큰 유스 케이스는 설명하기가 어렵지만, 모두 값 유형에 대한 연산을 수행 할 수있는 Action 팩토리를 작성하는 데만 사용됩니다. –

답변

24

이것은 불가능합니다.

컴파일러는 익명 메소드가 사용하는 모든 로컬 변수와 매개 변수를 자동 생성 된 클로저 클래스의 필드로 변환합니다.

CLR에서는 ref 형식을 필드에 저장할 수 없습니다.

예를 들어 로컬 변수에 값 유형을 ref 매개 변수로 전달하면 값의 수명이 스택 프레임을 초과하여 확장됩니다.

public static class IntEx { 
    unsafe public static Action CreateIncrementer(int* reference) { 
     return() => { 
      *reference += 1; 
     }; 
    } 
} 

그러나, 가비지 컬렉터는 가비지 컬렉션시 참조를 이동하여이 함께 위력을 과시 수 :

30

좋아, 나는 안전하지 않은 상황에서 경우 실제로 포인터로 가능하다는 것을 발견했습니다 다음은 다음을 나타냅니다.

변수를 메모리의 특정 지점에 고정하여이 문제를 해결할 수 있습니다. 이것은 생성자에 다음을 추가하여 수행 할 수 있습니다

우리가 찾고있는 정확하게 주변의 물체를 이동 가비지 컬렉터를 유지, 그래서
public Program() { 
     GCHandle.Alloc(_i, GCHandleType.Pinned); 
    } 

. 그러나 핀을 해제하기 위해 소멸자를 추가해야하고 객체의 수명이 다할 때까지 메모리가 조각납니다. 별로 쉽지 않습니다. 이것은 C++에서 더 많은 의미를 갖게 될 것인데, 물건이 움직이지 않고 자원 관리가 과정과 비슷하지만, C#에서는 모든 것이 자동적 인 것으로 간주됩니다.

이야기의 도덕적 인 것처럼 보입니다. 회원 유형을 참조 유형으로 묶어서 처리하면됩니다.

(그렇습니다. 질문을하기 전에 작동하는 방법이었습니다.하지만 내 모든 것을 제거 할 수있는 방법이 있는지 알아 내려고했습니다. 참조 < 멤버 변수 > 그리고 그냥 일반 int를 사용하십시오. 오, 그렇습니다.)

+4

+1 안전하지 않은 컨텍스트를 사용하는 관리되는 모드의 흥미로운 해결 방법. –

+0

GCHandle.Alloc에 ​​대한 필요성이 없습니다 - 고정 된 문장 중괄호를 확장하십시오. –

+0

@ Mr.TA 이는 GC가 일을 망칠 수 있음을 보여주는 예일뿐입니다. incr' 함수의 가치있는 사용은 그것을 생성하는 메소드에서 그것을 반환하거나 영구 객체에 멤버 변수로 추가합니다. 멤버 변수는 분명히 "고정 된"범위를 벗어납니다. –

2

변수가 지속성을 유지할 수있는 메커니즘을 사용하여 변수 참조를 만들 수 있도록하는 것이 유용한 기능이었을 수 있습니다. 이러한 기능을 사용하면 인덱서가 배열처럼 동작 할 수 있습니다 (예 : Dictionary < Int32, Point>는 "myDictionary [5] .X = 9;"를 통해 액세스 할 수 있습니다).이러한 참조가 다른 유형의 객체로 다운 캐스팅되거나 필드로 사용되거나 참조 자체에 의해 전달 될 수없는 경우 이러한 참조가 안전하게 제공 될 수 있다고 생각합니다 (참조가 저장 될 수있는 모든 위치는 참조 이전에 범위를 벗어날 수 있으므로 그 자체). 불행히도 CLR은 이러한 기능을 제공하지 않습니다.

클로저 내에서 참조 매개 변수를 사용하는 함수이 클로저 내에있는 모든 함수에 전달해야하는 경우 해당 함수에 전달할 변수를 호출자에게 전달해야합니다. 매개 변수가 그런 방식으로 사용될 것이라는 것을 나타내는 특별한 선언이 있다면 컴파일러가 필요한 동작을 구현하는 것이 현실적 일 수 있습니다. 어쩌면 .net 5.0 컴파일러에서 유용할지 모르겠지만.

나는 이해가 Java에 의해 가치 가치 의미론, 그 중. NET에있는 참조에 의해 닫혀있다. 참조 구문에 대한 몇 가지 사용법을 이해할 수 있지만 기본적으로 참조를 사용하는 것은 모호한 결정입니다. VB6을 통해 VB 버전에 대한 기본 참조 매개 변수 전달 의미를 사용하는 것과 유사합니다. 함수를 호출하기 위해 델리게이트를 생성 할 때 (예를 들어 델리게이트가 생성 될 때 델리게이트가 X 값을 사용하여 MyFunction (X)를 호출하기를 원할 경우) 변수의 값을 캡처하려면 람다 여분의 임시 직원과 함께, 또는 단순히 대리자 공장을 사용하고 람다 식을 귀찮게하지 않는 것이 좋습니다.

+1

예, 에릭 리 퍼트 (Eric Lippert)가 그것에 관한 블로그를 작성했습니다. http://blogs.msdn.com/b/ericlippert/archive/2009/11/12/closing-over-the-loop-variable-considered-harmful.aspx. 실제로이 동작에 의존하는 코드가 있습니다. 예를 들어 framecount 멤버 변수가 필요없이 OpenGL 루프에 대한 프레임 수를 유지합니다. 저는 C++ 스펙이 더 좋아서 어느 의미론도 가능합니다. –

+1

@Dax : pass-by-reference 의미가 필요한 경우가 있습니다. 그러나 매개 변수 전달도 마찬가지입니다. 그렇다고해서 통과 기준이 기본값이되어야 함을 의미하지는 않습니다. BTW, 나는 by-reference 클로저 변수를 단일 엘리먼트 배열로 대체하여 어떤 종류의 클로저 변수 (GC 문제를 피함)가 필요한 익명 메소드에 대해 다른 클래스를 작성할 수 있도록 장단점을 만들 었는지 궁금합니다. – supercat