2010-02-07 25 views
16

delegate Bu yüzden delegate nesnesini ilk oluşturduğumda bilmediğim bazı işlevlere işaret eden bir delegate var. Nesne daha sonra bazı işlevlere ayarlanır.İfade Ağaçları ve Bir Delege İçeren

Ayrıca bir argüman ile delegeyi çağırır bir ifade ağacı yapmak istiyorum (Bu sorunun uğruna argüman 5 olabilir). Bu, uğraştığım şey; Aşağıdaki kod ne istediğimi gösterir ama derleme yapmaz.

Func<int, int> func = null; 
Expression<Func<int>> expr =() => func(5); 

Bu expr haline yapar:

() => Invoke(value(Test.Program+<>c__DisplayClass0).func, 5) 

görünüyor Hangi

yapabileceğim Bu örnek için
Func<int, int> func = null; 
Expression expr = Expression.Invoke(func, Expression.Constant(5)); 

(Ben zamanında ifade ağaçlar oluşturmamız gerekir çünkü bu pratiktir) delegatefunc'u kullanmak için, value(Test.Program+<>c__DisplayClass0).func bitini üretmem gerekiyor.

Peki, nasıl bir temsilci çağrısı yapan bir ifade ağacı yapabilirim?

Action<int> func = i => Console.WriteLine(i * i); 

// If func is null like in your example, the GetType() call fails, 
// so give it a body or use typeof if you know the type at compile time 
var param = Expression.Parameter(func.GetType()); 

// Call the Invoke method on the delegate, which is the same as invoking() it 
var callExpr = Expression.Call(param, func.GetType().GetMethod("Invoke"), Expression.Constant(5)); 

var lambdaExpr = Expression.Lambda<Action<Action<int>>>(callExpr, param); 

var fn = lambdaExpr.Compile(); // Compile the expression tree so it can be executed 

fn(func); // Prints 25 

İfadeler mindfuck olabilir, ama unutmayın:

+0

[Linq in Action] (http://www.manning.com/marguerie/) İfade ağaçlarının ayrıntılı bir bölümüne sahiptir. –

cevap

9

OK: JulianR en numunenin üzerine inşa edilen buna benzerdi olacaktınız Esasen

Func<int, int> func = null; 
Expression<Func<int, int>> bind = (x) => func(x); 

Expression expr = Expression.Invoke(bind, Expression.Constant(5)); 

Expression<Func<int>> lambda = Expression.Lambda<Func<int>>(expr); 
Func<int> compiled = lambda.Compile(); 

Console.WriteLine(expr); 

func = x => 3 * x; 
Console.WriteLine(compiled()); 

func = x => 7 * x; 
Console.WriteLine(compiled()); 

Console.Read(); 

Temsilcinin işaret ettiği şeyi çağıran bir işlev yapmak için (x) => func(x); kullanıyorum. Ancak, expr'un aşırı karmaşık olduğunu görebilirsiniz.Bu nedenle bu cevabı iyi düşünmüyorum, ama belki de üzerine inşa edilebilir?

+2

Oluşturduğunuz ifadenin, func değişkenine başvuruyla erişmenin bir yolu olması gerekir. Yerel değişkene referans oluşturamazsınız. Kodunuzda yerel değişkeni yakalamak için cilt lambda'sını kullanıyorsunuz ve C# derleyicisi büyüsünü yerine getiriyor ve yerel bir değişken gibi görünenleri tutmak için ayrı bir sınıf yaratıyor. Bunu kendiniz yapabilirdiniz ve bağlama ifadesini kullanmak zorunda kalmayacaksınız, ancak ortaya çıkan lambda muhtemelen aynı derecede karmaşık olacaktır. –

+2

"Kendin yap yöntemini" görmek isterdim. Reflection.Emit'ten biraz sihir gerektireceğini hayal ediyorum? –

2

Bu çalışması gerekir ifadeler her zaman diğer ifadeler üzerinden inşa edilir. Bir ifade, kodu tanımlayan diğer ifadelerin bir ağacıdır. Örneğinizde yaptığınız gibi gerçek delegeyi geçemezsiniz, İhtiyacınız olan şey, temsilci temsilcinizin türünün bir parametresini beklediğini söyleyerek o delege ifadesinin bir ifadesidir. Sonra, '5' argümanı ile bu parametrede bir yöntem, yani Invoke yöntemini çağırmak istediğinizi söylüyorsunuz. Bundan sonraki tüm diğer şeyler, eğer ifadeyi muhtemelen yapabildiğiniz çalıştırılabilir kod haline getirmek istiyorsanız.

Bunu .NET4 ile çalıştırdım, umarım yalnızca .NET4 ifadesiyle karıştırılmadım. PythonPower yorumuna cevaben

DÜZENLEME:

Sana (bağımsız değişken olarak temsilci geçmediğine) temsilci kendisi bir ifadesi olarak tarif edildiğinde yalnızca bu gibi yapılabileceğini istediğini düşünüyorum:

var arg = Expression.Parameter(typeof(int), "i"); 

var multiply = Expression.Multiply(arg, arg); 

var writeln = Expression.Call(typeof(Console).GetMethod("WriteLine", 
    new[] { typeof(int) }), multiply); 

var lambda = Expression.Lambda<Action<int>>(writeln, arg); 

var compiled = lambda.Compile(); 

compiled(5); // Prints 25 

Düşünebildiğim tek diğer yolu, bir kapatma işleminde yerel olarak bildirilen bir temsilci yakalamaktır, ancak bunu nasıl yapacağımı bilmem.

+0

.NET Framework 3.5 ile çalışmasını sağlamak için yalnızca küçük bir değişiklik – Kane

+0

Bu, istediğim şeye çok yakın, ancak temsilci bir argüman olarak geçmek istemiyorum. –

+0

Düzenleme, işlevi bildiğimi varsayar, ancak İfadenin oluşturulmasından sonra işlevini bilmiyorum. Ve işlev mutlaka bir ifadenin kendisi değildir. Kapanış fikri umut verici geliyor. –

13

Yapmak istediğiniz şey, bir çağrı ifadesi oluşturmak için geçmek için temsilci Hedef ve Yöntem özelliklerini kullanmak olduğunu düşünüyorum. , Işte bu yapılabilir gösterir (ancak bence çok çirkin olduğunu)

Action<int> func = i => Console.WriteLine(i * i); 

var callExpr = Expression.Call(Expression.Constant(func.Target), func.Method, Expression.Constant(5)); 

var lambdaExpr = Expression.Lambda<Action>(callExpr); 
var fn = lambdaExpr.Compile(); 
fn(); // Prints 25 
+0

Çalışmasını sağlamak için Expression.Constant (func.Target) öğesini null ile değiştirmeliyim. Fakat bu, func işlevinin şu anda daha sonra gösterebileceği noktaya işaret etmediğine bağlanır. Sorun şu ki, func'nin hangi noktalarda yapılacağı konusunda bir varsayım yoktur ve herhangi bir zamanda değişebilir. –

+0

Neredeyse ihtiyacım olan cevap budur, ancak lütfen ilk parametreyi Expression.Call'dan kaldırın. Aksi takdirde, bir istisna vardır (hem .NET4 hem de .NET4.5'te işaretliyim) – ironic

+0

@ironic: Hedef yöntem, sınandığınız sırada statik bir yöntem miydi? Bu fark olabilir. –