Aracılığıyla paylaş


İfade ağaçlarını çalıştır

İfade ağacı, bazı kodları temsil eden bir veri yapısıdır. Derlenmiş veya yürütülebilir kod değildir. 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ı, LambdaExpression veya Expression<TDelegate> türündedir. Bu ifade ağaçlarını yürütmek için önce Compile metodunu çağırarak yürütülebilir bir temsilci oluşturun ve ardından bu temsilciyi çağırın.

Uyarı

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

İ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.

Lambda ifadelerinden işlevlere

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.ConstantExpression yürütmenin ne anlama geldiğini düşünün. Yararlı bir şey ifade eder mi?) Herhangi bir ifade ağacı, veya bir System.Linq.Expressions.LambdaExpression olan ya da LambdaExpression türünden türetilen, 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 Delegate türünü döndürür. 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, Expression<Func<int>> tarafından temsil edilen bir ifade ağacı, Func<int> türünde 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ü, bir ifade ağacını yürütülebilir koda dönüştürmek için kullanacağınız System.Linq.Expressions.LambdaExpression ve LambdaExpression.Compile üyelerini içerir. Compile yöntemi bir temsilci oluşturur. CompileToMethod yöntemi, ifade deyim ağacının derlenmiş çıkışını temsil eden IL ile bir System.Reflection.Emit.MethodBuilder nesneyi 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 ayrıntılı 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

Kod, LambdaExpression.Compile() çağrıldığında oluşturulan temsilciyi çağırarak yürütülür. Yukarıdaki kod, add.Compile()bir temsilci döndürür. Bu temsilciyi, kodu yürüten func() çağırarak kullanı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. Fazladan LambdaExpression.Compile() çağrılarından kaçınarak tasarruf ettiğiniz işlem süresi, iki farklı ifade ağacının aynı yürütülebilir koda yol açıp açmadığını belirleyen kodun çalıştırılması için 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 IDisposable uygulayan bir değişkene erişiyorsa, kodunuz nesneyi ifade ağacı tarafından hâlâ tutulurken imha edebilir.

Ö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şken constant için bir başvuru yakaladı. Bu değişkene, CreateBoundFunc tarafından döndürülen işlev yürütüldüğünde, daha sonraki herhangi bir zamanda erişilir.

Ancak, System.IDisposable implement eden aşağıdaki (biraz uydurma olan) sınıfı gö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;
    }
}

Bir ifadede, aşağıdaki kodda gösterildiği gibi kullanırsanız, System.ObjectDisposedException özelliği tarafından başvurulan kodu yürütürken Resource.Argument alırsınız.

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;
    }
}

Yöntemden döndürülen temsilci, yok edilmiş constant nesnesini kapsıyor. (Bir beyan using ifadesinde bildirildiği için imha edilmiştir.)

Şimdi, bu yöntemden döndürülen temsilciyi çalıştırdığınızda yürütme noktasında bir ObjectDisposedException fırlatılır.

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.