2011-10-28 12 views
9

Gdy używam Expression.Lambda(...).Compile() w celu utworzenia delegata z drzewa wyrażeń, wynikiem jest delegat, którego pierwszym argumentem jest Closure.Kompilowanie wyrażenia lambda powoduje delegowanie z argumentem zamknięcia

public static Func<T, T, T> CreateTest<T>() 
{ 
    ParameterExpression a = Expression.Parameter(typeof(T)); 
    ParameterExpression b = Expression.Parameter(typeof(T)); 
    Expression addition = Expression.Add(a, b); 

    return (Func<T, T, T>)Expression.Lambda(addition, a, b).Compile(); 
} 

... 

// 'addition' equals 
// Int32 lambda_method(
//  System.Runtime.CompilerServices.Closure, 
//  Int32, 
//  Int32) 
Func<int, int, int> addition = DelegateHelper.CreateTest<int>(); 
int result = addition(5, 5); 

mogę łatwo wywołać delegata za pomocą kodu zwykłego bez przepuszczania Closure obiekt, ale skąd ten Closure pochodzi?

Jak mogę dynamicznie wywoływać tego delegata?

// The following does not work. 
// Exception: MethodInfo must be a runtime MethodInfo object.  
MethodInfo additionMethod = addition.Method; 
int result = (int)additionMethod.Invoke(null, new object[] { 5, 5 }); 

Korzystanie z drzew wyrażeń wygląda tak, jakbym musiał minąć obiekt Closure.

PropertyInfo methodProperty 
    = typeof(Delegate).GetProperty("Method", typeof(MethodInfo)); 
MemberExpression getDelegateMethod 
    = Expression.Property(Expression.Constant(addition), methodProperty); 
Func<MethodInfo> getMethodInfo 
    = (Func<MethodInfo>)Expression.Lambda(getDelegateMethod).Compile(); 
// Incorrect number of arguments supplied for call to method 
// 'Int32 lambda_method(System.Runtime.CompilerServices.Closure, Int32, Int32)' 
Expression call 
    = Expression.Call(
     getMethodInfo(), 
     Expression.Constant(5), Expression.Constant(5)); 

Jest to uproszczony przykład, który nie ma sensu sam w sobie. To, co faktycznie próbuję osiągnąć, to móc owijać, np. Func<Action<SomeObject>> z Func<Action<object>>. Mogę już to zrobić dla nie zagnieżdżonych delegatów. Jest to przydatne podczas refleksji, as discussed here.

Jak poprawnie zainicjować ten obiekt Closure lub jak zapobiec jego występowaniu?

+2

Czy możesz podać krótki, ale * kompletny * przykład? Nie jest tak naprawdę oczywiste, jaki jest problem. –

+1

@JonSkeet: Zrobię co w mojej mocy, problem polega na tym, że ogólny przykład jest dość złożony. Próbuję wywoływać rekurencyjnie wcześniej skompilowanego delegata. Podczas gdy próbuję wyodrębnić mały podzbiór problemu, [tutaj] (http://pastebin.com/53f0VqnF) można już znaleźć całą funkcję. –

+0

Tak, skrócenie tego z pewnością pomogłoby :) –

Odpowiedz

9

Typ Closure, który widzisz, jest szczegółem implementacji. MSDN jest dość wyraźnie o nim:

Ten interfejs API obsługuje infrastrukturę programu .NET Framework i nie jest przeznaczony do użycia bezpośrednio w kodzie. Reprezentuje stan runtime stanu dynamicznie generowanej metody.

Drzewo wyrażeń może mieć stan.

Instancja zamknięcia będzie zawierała wszystkie niesłowne stałe, które wyrażenie lambda, dobrze, zamyka się. Może również zawierać łańcuch delegatów dla zagnieżdżonych lambdas w drzewach wyrażeń.

Aby to osiągnąć, kompilator drzewa wyrażeń używa uroczej małej sztuczki. Generuje on w pamięci kod przy użyciu DynamicMethod, który jest z definicji statyczny. Jednak tworzą delegata, który jest “closed over its first argument”. Oznacza to, że CLR przejdzie przez pole docelowe delegata jako pierwszy argument metody statycznej, więc nie musisz tego robić. Skutecznie ukrywając przed tobą argument zamknięcia.

Rozwiązanie twojego problemu jest proste, nie próbuj wywoływać metody, wywołaj delegata, używając Delegate.DynamicInvoke kiedy używasz odbicia lub Expression.Invoke w kontekście drzewa wyrażeń.

+0

To świetnie, dzięki! Moja funkcja działała rekurencyjnie, wywołując się przy pomocy nowej metody "MethodInfo". Stworzyłem nowy, który przyjmuje zamiast tego delegata i używa go do wyrażenia "Expression.Invoke". Po kilku poważnych zmianach wprowadzę zmiany. Mogę teraz z powodzeniem owijać np. 'Func >' with 'Func >'. Ogólne obliczenia z silnymi wyliczeniami typowanymi. :) Kocham to. 'IEnumerable setFlags = EnumHelper .GetFlaggedValues ​​(flags);' –

+0

K, jest to możliwe również w .NET 4.0, używając 'Enum', ale mogę użyć tego" opakowania pełnomocnika "w wielu innych scenariuszach. Zrobiłem krótkie porównanie wydajności z 'Enum', a różnica jest znikoma. –