.net Func <T> 'yi .net İfadesine dönüştürme <Func <T>>


118

Bir lambda'dan bir İfadeye gitmek, bir yöntem çağrısı kullanarak kolaydır ...

public void GimmeExpression(Expression<Func<T>> expression)
{
    ((MemberExpression)expression.Body).Member.Name; // "DoStuff"
}

public void SomewhereElse()
{
    GimmeExpression(() => thing.DoStuff());
}

Ancak Func'u bir ifadeye dönüştürmek istiyorum, sadece nadir durumlarda ...

public void ContainTheDanger(Func<T> dangerousCall)
{
    try 
    {
        dangerousCall();
    }
    catch (Exception e)
    {
        // This next line does not work...
        Expression<Func<T>> DangerousExpression = dangerousCall;
        var nameOfDanger = 
            ((MemberExpression)dangerousCall.Body).Member.Name;
        throw new DangerContainer(
            "Danger manifested while " + nameOfDanger, e);
    }
}

public void SomewhereElse()
{
    ContainTheDanger(() => thing.CrossTheStreams());
}

Çalışmayan satır bana derleme zamanı hatası veriyor Cannot implicitly convert type 'System.Func<T>' to 'System.Linq.Expressions.Expression<System.Func<T>>'. Açık bir atama durumu çözmez. Bunu yapmak için gözden kaçırdığım bir tesis var mı?


'Nadir durum' örneğinin pek bir faydasını görmüyorum. Arayan, Func <T> 'de geçiyor. Arayan kişiye Func <T> 'nin ne olduğunu (istisna yoluyla) tekrar etmenize gerek yoktur.
Adam Ralph

2
İstisna, arayan tarafından işlenmez. Ve, farklı Func <T> lerden geçen birden fazla çağrı sitesi olduğundan, arayandaki istisnanın yakalanması yineleme oluşturur.
Dave Cameron

1
İstisna yığını izleme, bu bilgileri göstermek için tasarlanmıştır. İstisna, Func <T> çağrısı içinde atılırsa, bu yığın izlemede gösterilecektir. Bu arada, diğer yoldan gitmeyi seçerseniz, yani bir ifadeyi kabul edip çağırma için derlerseniz, yığın izleme at lambda_method(Closure )derlenmiş temsilcinin çağrılması gibi bir şey göstereceği için bunu kaybedersiniz .
Adam Ralph

Sanırım bu [bağlantı] [1] [1] 'deki yanıta bakmalısınız: stackoverflow.com/questions/9377635/create-expression-from-func/…
İbrahim Kais Ibrahim

Yanıtlar:


104

Ooh, hiç de kolay değil. bir ifadeyi değil Func<T>genel delegatebir ifadeyi temsil eder . Bunu yapabileceğiniz herhangi bir yol varsa (derleyici tarafından yapılan optimizasyonlar ve diğer şeyler nedeniyle, bazı veriler atılabilir, bu nedenle orijinal ifadeyi geri almak imkansız olabilir), bu IL'yi anında parçalarına ayırmak olacaktır. ve ifadeyi çıkarmak (ki bu hiç de kolay değildir). Lambda ifadelerini data ( Expression<Func<T>>) olarak ele almak, derleyici tarafından yapılan bir sihirdir (temelde derleyici, onu IL'ye derlemek yerine kodda bir ifade ağacı oluşturur).

İlgili gerçek

Bu nedenle, lambdaları aşırıya iten dillerin (Lisp gibi) tercüman olarak uygulanması genellikle daha kolaydır . Bu dillerde, kod ve veriler temelde aynı şeydir ( çalışma zamanında bile ), ancak çipimiz bu kod biçimini anlayamaz, bu nedenle, böyle bir makineyi, onu anlayan bir yorumlayıcı oluşturarak taklit etmeliyiz ( Lisp tarafından diller gibi yapılan seçim) veya gücü bir dereceye kadar feda etme (kod artık tam olarak verilere eşit olmayacak) (C # tarafından yapılan seçim). C # 'da derleyici, lambdaların derleme zamanında kod ( Func<T>) ve veri ( Expression<Func<T>>) olarak yorumlanmasına izin vererek kodu veri olarak işleme yanılsamasını verir .


3
Lisp'in yorumlanması gerekmez, kolayca derlenebilir. Makroların derleme sırasında genişletilmesi evalgerekir ve desteklemek istiyorsanız derleyiciyi başlatmanız gerekir, ancak bunun dışında bunu yapmakta hiçbir sorun yoktur.
yapılandırıcı

2
"İfade <Func <T>> DangerousExpression = () => tehlikeliCall ();" Kolay değil?
mheyman

10
@mheyman Bu Expression, sarmalayıcı eyleminiz hakkında yeni şeyler yaratır , ancak dangerousCalltemsilcinin dahili öğeleri hakkında herhangi bir ifade ağacı bilgisine sahip olmaz .
Nenad

34
    private static Expression<Func<T, bool>> FuncToExpression<T>(Func<T, bool> f)  
    {  
        return x => f(x);  
    } 

1
Döndürülen ifadenin sözdizimi ağacını geçmek istedim. Bu yaklaşım bunu yapmama izin verir mi?
Dave Cameron

6
@DaveCameron - Hayır. Yukarıdaki cevaplara bakın - önceden derlenmiş Funcolan yeni bir İfade içinde gizlenecektir. Bu, kod üzerine yalnızca bir veri katmanı ekler; fDaha fazla ayrıntı olmadan sadece parametrenizi bulmak için bir katmanı geçebilirsiniz , böylece başladığınız yerdesiniz.
Jonno

21

Muhtemelen yapmanız gereken, yöntemi tersine çevirmektir. Bir İfade alın> ve derleyin ve çalıştırın. Başarısız olursa, bakmanız gereken İfade zaten var.

public void ContainTheDanger(Expression<Func<T>> dangerousCall)
{
    try 
    {
        dangerousCall().Compile().Invoke();;
    }
    catch (Exception e)
    {
        // This next line does not work...
        var nameOfDanger = 
            ((MemberExpression)dangerousCall.Body).Member.Name;
        throw new DangerContainer(
            "Danger manifested while " + nameOfDanger, e);
    }
}

public void SomewhereElse()
{
    ContainTheDanger(() => thing.CrossTheStreams());
}

Açıkçası, bunun performans etkilerini göz önünde bulundurmanız ve gerçekten yapmanız gereken bir şey olup olmadığına karar vermeniz gerekiyor.


7

Ancak .Compile () yöntemini kullanarak diğer yoldan gidebilirsiniz - bunun sizin için yararlı olup olmadığından emin değilsiniz:

public void ContainTheDanger<T>(Expression<Func<T>> dangerousCall)
{
    try
    {
        var expr = dangerousCall.Compile();
        expr.Invoke();
    }
    catch (Exception e)
    {
        Expression<Func<T>> DangerousExpression = dangerousCall;
        var nameOfDanger = ((MethodCallExpression)dangerousCall.Body).Method.Name;
        throw new DangerContainer("Danger manifested while " + nameOfDanger, e);
    }
}

public void SomewhereElse()
{
    var thing = new Thing();
    ContainTheDanger(() => thing.CrossTheStreams());
}

6

Bazen bir ifadeye, bazen de bir temsilciye ihtiyacınız varsa, 2 seçeneğiniz vardır:

  • farklı yöntemleri vardır (her biri için 1)
  • her zaman Expression<...>sürümü kabul edin ve .Compile().Invoke(...)bir temsilci istiyorsanız sadece onu. Açıkçası bunun bir maliyeti var.

6

NJection.LambdaConverter , temsilcileri ifadeye dönüştüren bir kitaplıktır

public class Program
{
    private static void Main(string[] args) {
       var lambda = Lambda.TransformMethodTo<Func<string, int>>()
                          .From(() => Parse)
                          .ToLambda();            
    }   

    public static int Parse(string value) {
       return int.Parse(value)
    } 
}

4
 Expression<Func<T>> ToExpression<T>(Func<T> call)
        {
            MethodCallExpression methodCall = call.Target == null
                ? Expression.Call(call.Method)
                : Expression.Call(Expression.Constant(call.Target), call.Method);

            return Expression.Lambda<Func<T>>(methodCall);
        }

"Bu işe yaramayacak" kısmını biraz açabilir misin? Gerçekten onu derlemeyi ve çalıştırmayı denediniz mi? Veya özellikle uygulamanızda çalışmıyor mu?
Dmitry Dzygin

1
FWIW, ana bilet bu olmayabilir, ama ihtiyacım olan buydu. Öyleydi call.Targetbeni öldürüyordu parçası. Yıllarca çalıştı ve sonra aniden çalışmayı bıraktı ve statik / statik olmayan falan filan hakkında şikayet etmeye başladı. Her neyse, teşekkürler!
Eli Gassert


-1

Değişiklik

// This next line does not work...
Expression<Func<T>> DangerousExpression = dangerousCall;

için

// This next line works!
Expression<Func<T>> DangerousExpression = () => dangerousCall();

Servy, bir ifade elde etmenin kesinlikle yasal bir yolu. sözdizimi şekeri oluşturmak için expression.lambda ve expression.call. Neden çalışma zamanında başarısız olması gerektiğini düşünüyorsunuz?
Roman Pokrovskij
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.