Aracılığıyla paylaş


İfade ağaçlarını yürütme

İfade ağacı , bazı kodları temsil eden bir veri yapısıdır. Derlenmemiş ve yürütülebilir kod. bir ifade ağacıyla temsil edilen .NET kodunu yürütmek istiyorsanız, bunu yürütülebilir IL yönergelerine dönüştürmeniz gerekir. İfade ağacının yürütülmesi bir değer döndürebilir veya yalnızca yöntem çağırma gibi bir eylem gerçekleştirebilir.

Yalnızca lambda ifadelerini temsil eden ifade ağaçları yürütülebilir. Lambda ifadelerini temsil eden ifade ağaçları veya Expression<TDelegate>türündedirLambdaExpression. Bu ifade ağaçlarını yürütmek için yöntemini çağırarak Compile yürütülebilir bir temsilci oluşturun ve temsilciyi çağırın.

Not

Temsilcinin türü bilinmiyorsa, yani lambda ifadesi türünde LambdaExpression ve değil Expression<TDelegate>türündeyse, doğrudan çağırmak yerine temsilcide yöntemini çağırın DynamicInvoke .

İfade ağacı bir lambda ifadesini temsil etmiyorsa, yöntemini çağırarak Lambda<TDelegate>(Expression, IEnumerable<ParameterExpression>) özgün ifade ağacını gövdesi olarak içeren yeni bir lambda ifadesi oluşturabilirsiniz. Ardından, bu bölümün önceki bölümlerinde açıklandığı gibi lambda ifadesini yürütebilirsiniz.

İşlevlere lambda ifadeleri

Herhangi bir LambdaExpression'ı veya LambdaExpression'dan türetilen herhangi bir türü yürütülebilir IL'ye dönüştürebilirsiniz. Diğer ifade türleri doğrudan koda dönüştürülemez. Bu kısıtlamanın uygulamada çok az etkisi vardır. Lambda ifadeleri, yürütülebilir ara dile (IL) dönüştürerek yürütmek istediğiniz tek ifade türleridir. (Doğrudan bir System.Linq.Expressions.ConstantExpressionyürütmenin ne anlama gelir olduğunu düşünün. Yararlı bir şey ifade eder mi?) bir veya türünden System.Linq.Expressions.LambdaExpressionLambdaExpression türetilen herhangi bir ifade ağacı IL'ye dönüştürülebilir. İfade türü System.Linq.Expressions.Expression<TDelegate> , .NET Core kitaplıklarındaki tek somut örnektir. Herhangi bir temsilci türüne eşleyen bir ifadeyi temsil etmek için kullanılır. Bu tür bir temsilci türüyle eşlendiğinden, .NET ifadeyi inceleyebilir ve lambda ifadesinin imzası ile eşleşen uygun bir temsilci için IL oluşturabilir. Temsilci türü, ifade türünü temel alır. Temsilci nesnesini kesin olarak belirlenmiş bir şekilde kullanmak istiyorsanız, dönüş türünü ve bağımsız değişken listesini bilmeniz gerekir. LambdaExpression.Compile() yöntemi türünü döndürürDelegate. Derleme zamanı araçlarının bağımsız değişken listesini veya dönüş türünü denetlemesini sağlamak için bunu doğru temsilci türüne dönüştürmeniz gerekir.

Çoğu durumda, bir ifade ve buna karşılık gelen temsilcisi arasında basit bir eşleme vardır. Örneğin, tarafından Expression<Func<int>> temsil edilen bir ifade ağacı türünde Func<int>bir temsilciye dönüştürülür. Herhangi bir dönüş türüne ve bağımsız değişken listesine sahip bir lambda ifadesi için, bu lambda ifadesi tarafından temsil edilen yürütülebilir kodun hedef türü olan bir temsilci türü vardır.

Türü, System.Linq.Expressions.LambdaExpression bir ifade ağacını yürütülebilir koda dönüştürmek için kullanacağınız ve LambdaExpression.CompileToMethod üyelerini içerirLambdaExpression.Compile. Compile yöntemi bir temsilci oluşturur. yöntemi, CompileToMethod bir System.Reflection.Emit.MethodBuilder nesneyi ifade ağacının derlenmiş çıkışını temsil eden IL ile güncelleştirir.

Önemli

CompileToMethod yalnızca .NET Framework'te kullanılabilir, .NET Core veya .NET 5 ve sonraki sürümlerde kullanılamaz.

İsteğe bağlı olarak, oluşturulan temsilci nesnesi için sembol hata ayıklama bilgilerini alan bir System.Runtime.CompilerServices.DebugInfoGenerator de sağlayabilirsiniz. , DebugInfoGenerator oluşturulan temsilci hakkında tam hata ayıklama bilgileri sağlar.

Aşağıdaki kodu kullanarak bir ifadeyi temsilciye dönüştürebilirsiniz:

Expression<Func<int>> add = () => 1 + 2;
var func = add.Compile(); // Create Delegate
var answer = func(); // Invoke Delegate
Console.WriteLine(answer);

Aşağıdaki kod örneği, bir ifade ağacını derleyip yürütürken kullanılan somut türleri gösterir.

Expression<Func<int, bool>> expr = num => num < 5;

// Compiling the expression tree into a delegate.
Func<int, bool> result = expr.Compile();

// Invoking the delegate and writing the result to the console.
Console.WriteLine(result(4));

// Prints True.

// You can also use simplified syntax
// to compile and run an expression tree.
// The following line can replace two previous statements.
Console.WriteLine(expr.Compile()(4));

// Also prints True.

Aşağıdaki kod örneğinde, lambda ifadesi oluşturup çalıştırarak bir sayıyı bir güce yükseltmeyi temsil eden bir ifade ağacının nasıl yürütüldiği gösterilmektedir. Güce yükseltilen sayıyı temsil eden sonuç görüntülenir.

// The expression tree to execute.
BinaryExpression be = Expression.Power(Expression.Constant(2d), Expression.Constant(3d));

// Create a lambda expression.
Expression<Func<double>> le = Expression.Lambda<Func<double>>(be);

// Compile the lambda expression.
Func<double> compiledExpression = le.Compile();

// Execute the lambda expression.
double result = compiledExpression();

// Display the result.
Console.WriteLine(result);

// This code produces the following output:
// 8

Yürütme ve yaşam süreleri

çağırdığınızda oluşturulan temsilciyi çağırarak kodu yürütürsiniz LambdaExpression.Compile(). Yukarıdaki kod, add.Compile()bir temsilci döndürür. Kodu yürüten çağırarak func()bu temsilciyi çağırırsınız.

Bu temsilci, ifade ağacındaki kodu temsil eder. Bu temsilcinin tutamacını koruyabilir ve daha sonra çağırabilirsiniz. Temsil ettiği kodu her yürütmek istediğinizde ifade ağacını derlemeniz gerekmez. (İfade ağaçlarının sabit olduğunu ve aynı ifade ağacının derlenmesi daha sonra aynı kodu yürüten bir temsilci oluşturduğunu unutmayın.)

Dikkat

Gereksiz derleme çağrılarından kaçınarak performansı artırmak için daha gelişmiş önbelleğe alma mekanizmaları oluşturmayın. İki rastgele ifade ağacını karşılaştırarak aynı algoritmayı temsil etmelerini belirlemek zaman alan bir işlemdir. ek çağrılardan kaçınarak kaydettiğiniz işlem süresi, iki farklı ifade ağacının aynı yürütülebilir koda LambdaExpression.Compile() neden olup olmadığını belirleyen kod yürütülürken harcanan süreden daha fazla olabilir.

Uyarılar

Bir lambda ifadesini bir temsilciye derlemek ve bu temsilciyi çağırmak, bir ifade ağacıyla gerçekleştirebileceğiniz en basit işlemlerden biridir. Ancak, bu basit işlemle bile, bilmeniz gereken uyarılar vardır.

Lambda İfadeleri, ifadede başvuruda bulunan tüm yerel değişkenler üzerinde kapanışlar oluşturur. Temsilcinin parçası olacak tüm değişkenlerin, öğesini çağırdığınız Compilekonumda ve sonuçta elde edilen temsilciyi yürütürken kullanılabilir olacağını garanti etmeniz gerekir. Derleyici, değişkenlerin kapsamda olmasını sağlar. Ancak, ifadeniz uygulayan IDisposablebir değişkene erişiyorsa, kodunuz ifade ağacı tarafından tutulurken nesnenin atılması mümkündür.

Örneğin, bu kod düzgün çalışır, çünkü int uygulamaz IDisposable:

private static Func<int, int> CreateBoundFunc()
{
    var constant = 5; // constant is captured by the expression tree
    Expression<Func<int, int>> expression = (b) => constant + b;
    var rVal = expression.Compile();
    return rVal;
}

Temsilci, yerel değişkenine constantbir başvuru yakaladı. Bu değişkene, tarafından döndürülen CreateBoundFunc işlev yürütürken daha sonra herhangi bir zamanda erişilir.

Ancak, uygulayan aşağıdaki (daha çok contrived) sınıfını System.IDisposablegöz önünde bulundurun:

public class Resource : IDisposable
{
    private bool _isDisposed = false;
    public int Argument
    {
        get
        {
            if (!_isDisposed)
                return 5;
            else throw new ObjectDisposedException("Resource");
        }
    }

    public void Dispose()
    {
        _isDisposed = true;
    }
}

Bunu aşağıdaki kodda gösterildiği gibi bir ifadede kullanırsanız, özelliği tarafından Resource.Argument başvuruda bulunan kodu yürütürken alırsınızSystem.ObjectDisposedException:

private static Func<int, int> CreateBoundResource()
{
    using (var constant = new Resource()) // constant is captured by the expression tree
    {
        Expression<Func<int, int>> expression = (b) => constant.Argument + b;
        var rVal = expression.Compile();
        return rVal;
    }
}

Bu yöntemden döndürülen temsilci, atılan nesnesi üzerinde constant kapatıldı. (Bir deyimde using bildirildiği için atılmıştır.)

Şimdi, bu yöntemden döndürülen temsilciyi yürütürken yürütme noktasında bir ObjectDisposedException oluşturulur.

Derleme zamanı yapısını temsil eden bir çalışma zamanı hatasının olması garip görünebilir, ancak ifade ağaçlarıyla çalışırken girdiğiniz dünya bu.

Bu sorunun çok sayıda permütasyonu vardır, bu nedenle bu sorundan kaçınmak için genel rehberlik sunmak zordur. İfadeleri tanımlarken yerel değişkenlere erişmeye dikkat edin ve genel API aracılığıyla döndürülen bir ifade ağacı oluştururken geçerli nesnedeki (ile thistemsil edilen) duruma erişme konusunda dikkatli olun.

İfadenizdeki kod diğer derlemelerdeki yöntemlere veya özelliklere başvurabilir. İfade tanımlandığında, derlendiğinde ve sonuçta elde edilen temsilci çağrıldığında bu derlemeye erişilebilir olmalıdır. Mevcut olmadığı durumlarda bir ReferencedAssemblyNotFoundException ile karşılaşıyorsunuz.

Özet

Lambda ifadelerini temsil eden İfade Ağaçları, yürütebileceğiniz bir temsilci oluşturmak için derlenebilir. İfade ağaçları, bir ifade ağacıyla temsil edilen kodu yürütmek için bir mekanizma sağlar.

İfade Ağacı, oluşturduğunuz herhangi bir yapı için yürütülebilecek kodu temsil eder. Kodu derleyip yürüttüğüniz ortam, ifadeyi oluşturduğunuz ortamla eşleştiği sürece, her şey beklendiği gibi çalışır. Bu gerçekleşmediğinde, hatalar tahmin edilebilir olur ve ifade ağaçlarını kullanarak herhangi bir kodun ilk testlerinde yakalanırlar.