Not
Bu sayfaya erişim yetkilendirme gerektiriyor. Oturum açmayı veya dizinleri değiştirmeyi deneyebilirsiniz.
Bu sayfaya erişim yetkilendirme gerektiriyor. Dizinleri değiştirmeyi deneyebilirsiniz.
Aşağıdaki kod örneğinde lambda ifadesini temsil eden num => num < 5
ifade ağacının parçalarına nasıl ayrıştırılabildiği gösterilmektedir.
// Add the following using directive to your code file:
// using System.Linq.Expressions;
// Create an expression tree.
Expression<Func<int, bool>> exprTree = num => num < 5;
// Decompose the expression tree.
ParameterExpression param = (ParameterExpression)exprTree.Parameters[0];
BinaryExpression operation = (BinaryExpression)exprTree.Body;
ParameterExpression left = (ParameterExpression)operation.Left;
ConstantExpression right = (ConstantExpression)operation.Right;
Console.WriteLine($"Decomposed expression: {param.Name} => {left.Name} {operation.NodeType} {right.Value}");
// This code produces the following output:
// Decomposed expression: num => num LessThan 5
Şimdi ifade ağacının yapısını incelemek içinkod yazalım. İfade ağacındaki her düğüm, Expression
'dan türetilmiş bir sınıfın nesnesidir.
Bu tasarım, bir ifade ağacındaki tüm düğümlerin ziyaretini nispeten basit bir özyinelemeli işlem haline getirir. Genel strateji, kök düğümden başlayıp ne tür bir düğüm olduğunu belirlemektir.
Düğüm türünün çocuk düğümleri varsa, çocuk düğümleri tekrarlayarak ziyaret edin. Her bir alt düğümde, kök düğümde kullanılan işlemi yineleyin: türü belirleyin ve türün alt düğümleri varsa, bu alt düğümlerin her birini ziyaret edin.
Alt öğe içermeyen bir ifadeyi inceleyin
Basit bir ifade ağacındaki her düğümü ziyaret ederek başlayalım. Sabit bir ifade oluşturan ve ardından özelliklerini inceleyen kod aşağıdadır:
var constant = Expression.Constant(24, typeof(int));
Console.WriteLine($"This is a/an {constant.NodeType} expression type");
Console.WriteLine($"The type of the constant value is {constant.Type}");
Console.WriteLine($"The value of the constant value is {constant.Value}");
Yukarıdaki kod aşağıdaki çıkışı yazdırır:
This is a/an Constant expression type
The type of the constant value is System.Int32
The value of the constant value is 24
Şimdi bu ifadeyi inceleyecek kodu yazalım ve bu ifadeyle ilgili bazı önemli özellikleri yazalım.
Ekleme ifadesi
Bu bölüme girişteki toplama örneğini kullanarak başlayalım.
Expression<Func<int>> sum = () => 1 + 2;
Uyarı
Temsilcinin doğal türü Expression<Func<int>>
değil Func<int>
olduğundan, bu ifade ağacını bildirmek içinvar
kullanmayın.
Kök düğümü bir LambdaExpression
'dır.
=>
işlecinin sağ tarafındaki ilginç koda erişmek için LambdaExpression
alt öğelerinden birini bulmanız gerekir. Bunu bu bölümdeki tüm ifadelerle yaparsınız. Üst düğüm, LambdaExpression
dönüş türünü bulmamıza yardımcı olur.
Bu ifadedeki her düğümü incelemek için birçok düğümü yinelemeli olarak ziyaret etmeniz gerekir. İşte basit bir ilk uygulama:
Expression<Func<int, int, int>> addition = (a, b) => a + b;
Console.WriteLine($"This expression is a {addition.NodeType} expression type");
Console.WriteLine($"The name of the lambda is {((addition.Name == null) ? "<null>" : addition.Name)}");
Console.WriteLine($"The return type is {addition.ReturnType.ToString()}");
Console.WriteLine($"The expression has {addition.Parameters.Count} arguments. They are:");
foreach (var argumentExpression in addition.Parameters)
{
Console.WriteLine($"\tParameter Type: {argumentExpression.Type.ToString()}, Name: {argumentExpression.Name}");
}
var additionBody = (BinaryExpression)addition.Body;
Console.WriteLine($"The body is a {additionBody.NodeType} expression");
Console.WriteLine($"The left side is a {additionBody.Left.NodeType} expression");
var left = (ParameterExpression)additionBody.Left;
Console.WriteLine($"\tParameter Type: {left.Type.ToString()}, Name: {left.Name}");
Console.WriteLine($"The right side is a {additionBody.Right.NodeType} expression");
var right = (ParameterExpression)additionBody.Right;
Console.WriteLine($"\tParameter Type: {right.Type.ToString()}, Name: {right.Name}");
Bu örnek aşağıdaki çıkışı yazdırır:
This expression is a/an Lambda expression type
The name of the lambda is <null>
The return type is System.Int32
The expression has 2 arguments. They are:
Parameter Type: System.Int32, Name: a
Parameter Type: System.Int32, Name: b
The body is a/an Add expression
The left side is a Parameter expression
Parameter Type: System.Int32, Name: a
The right side is a Parameter expression
Parameter Type: System.Int32, Name: b
Önceki kod örneğinde çok fazla yineleme olduğunu fark edeceksiniz. Şimdi bunu temizleyelim ve daha geniş bir kullanıma uygun bir ifade düğümü ziyaretçisi oluşturalım. Bunun için özyinelemeli bir algoritma yazmamız gerekecek. Herhangi bir düğüm, çocukları olabilecek bir türde olabilir. Çocuklu herhangi bir düğüm, bu çocukları ziyaret etmemizi ve bu düğümün ne olduğunu belirlememizi gerektirir. Toplama işlemlerini ziyaret etmek için özyineleme kullanan temizlenmiş sürüm aşağıdadır:
using System.Linq.Expressions;
namespace Visitors;
// Base Visitor class:
public abstract class Visitor
{
private readonly Expression node;
protected Visitor(Expression node) => this.node = node;
public abstract void Visit(string prefix);
public ExpressionType NodeType => node.NodeType;
public static Visitor CreateFromExpression(Expression node) =>
node.NodeType switch
{
ExpressionType.Constant => new ConstantVisitor((ConstantExpression)node),
ExpressionType.Lambda => new LambdaVisitor((LambdaExpression)node),
ExpressionType.Parameter => new ParameterVisitor((ParameterExpression)node),
ExpressionType.Add => new BinaryVisitor((BinaryExpression)node),
_ => throw new NotImplementedException($"Node not processed yet: {node.NodeType}"),
};
}
// Lambda Visitor
public class LambdaVisitor : Visitor
{
private readonly LambdaExpression node;
public LambdaVisitor(LambdaExpression node) : base(node) => this.node = node;
public override void Visit(string prefix)
{
Console.WriteLine($"{prefix}This expression is a {NodeType} expression type");
Console.WriteLine($"{prefix}The name of the lambda is {((node.Name == null) ? "<null>" : node.Name)}");
Console.WriteLine($"{prefix}The return type is {node.ReturnType}");
Console.WriteLine($"{prefix}The expression has {node.Parameters.Count} argument(s). They are:");
// Visit each parameter:
foreach (var argumentExpression in node.Parameters)
{
var argumentVisitor = CreateFromExpression(argumentExpression);
argumentVisitor.Visit(prefix + "\t");
}
Console.WriteLine($"{prefix}The expression body is:");
// Visit the body:
var bodyVisitor = CreateFromExpression(node.Body);
bodyVisitor.Visit(prefix + "\t");
}
}
// Binary Expression Visitor:
public class BinaryVisitor : Visitor
{
private readonly BinaryExpression node;
public BinaryVisitor(BinaryExpression node) : base(node) => this.node = node;
public override void Visit(string prefix)
{
Console.WriteLine($"{prefix}This binary expression is a {NodeType} expression");
var left = CreateFromExpression(node.Left);
Console.WriteLine($"{prefix}The Left argument is:");
left.Visit(prefix + "\t");
var right = CreateFromExpression(node.Right);
Console.WriteLine($"{prefix}The Right argument is:");
right.Visit(prefix + "\t");
}
}
// Parameter visitor:
public class ParameterVisitor : Visitor
{
private readonly ParameterExpression node;
public ParameterVisitor(ParameterExpression node) : base(node)
{
this.node = node;
}
public override void Visit(string prefix)
{
Console.WriteLine($"{prefix}This is an {NodeType} expression type");
Console.WriteLine($"{prefix}Type: {node.Type}, Name: {node.Name}, ByRef: {node.IsByRef}");
}
}
// Constant visitor:
public class ConstantVisitor : Visitor
{
private readonly ConstantExpression node;
public ConstantVisitor(ConstantExpression node) : base(node) => this.node = node;
public override void Visit(string prefix)
{
Console.WriteLine($"{prefix}This is an {NodeType} expression type");
Console.WriteLine($"{prefix}The type of the constant value is {node.Type}");
Console.WriteLine($"{prefix}The value of the constant value is {node.Value}");
}
}
Bu algoritma, herhangi bir rastgele LambdaExpression
ziyaret eden bir algoritmanın temelini oluşturur. Oluşturduğunuz kod yalnızca karşılaşabileceği ifade ağacı düğümlerinin küçük bir örneğini arar. Ancak, yine de ürettiklerinden biraz öğrenebilirsiniz. (Visitor.CreateFromExpression
yöntemindeki varsayılan durum, yeni bir düğüm türüyle karşılaşıldığında hata konsoluna bir ileti yazdırır. Bu şekilde, yeni bir ifade türü eklemeyi bilirsiniz.)
Bu ziyaretçiyi önceki toplama ifadesinde çalıştırdığınızda aşağıdaki çıkışı alırsınız:
This expression is a/an Lambda expression type
The name of the lambda is <null>
The return type is System.Int32
The expression has 2 argument(s). They are:
This is an Parameter expression type
Type: System.Int32, Name: a, ByRef: False
This is an Parameter expression type
Type: System.Int32, Name: b, ByRef: False
The expression body is:
This binary expression is a Add expression
The Left argument is:
This is an Parameter expression type
Type: System.Int32, Name: a, ByRef: False
The Right argument is:
This is an Parameter expression type
Type: System.Int32, Name: b, ByRef: False
Artık daha genel bir ziyaretçi uygulaması oluşturduğunuza göre, birçok farklı ifade türünü ziyaret edebilir ve işleyebilirsiniz.
Daha fazla operatör içeren Toplama İfadesi
Şimdi daha karmaşık bir örnek deneyelim, ancak yine de düğüm türlerini yalnızca eklemeyle sınırlayalım:
Expression<Func<int>> sum = () => 1 + 2 + 3 + 4;
Bu örnekleri ziyaretçi algoritmasında çalıştırmadan önce, çıkışın ne olabileceğini öğrenmek için bir düşünce alıştırması deneyin.
+
işlecinin birikili işleci olduğunu unutmayın: sol ve sağ işlenenleri temsil eden iki alt öğeye sahip olması gerekir. Doğru olabilecek bir ağaç oluşturmanın birkaç olası yolu vardır:
Expression<Func<int>> sum1 = () => 1 + (2 + (3 + 4));
Expression<Func<int>> sum2 = () => ((1 + 2) + 3) + 4;
Expression<Func<int>> sum3 = () => (1 + 2) + (3 + 4);
Expression<Func<int>> sum4 = () => 1 + ((2 + 3) + 4);
Expression<Func<int>> sum5 = () => (1 + (2 + 3)) + 4;
En umut vericiyi vurgulamak için iki olası yanıta ayırmayı görebilirsiniz. birincisi doğru ilişkilendirici ifadelerini temsil eder. İkincisi, sol ilişkilendirici ifadelerini temsil eder. Bu iki biçimin her ikisinin de avantajı, biçimin rastgele sayıda toplama ifadesine ölçeklendirilmesidir.
Bu ifadeyi ziyaretçi aracılığıyla çalıştırırsanız, basit ekleme ifadesinin sol ilişkilendirici olduğunu doğrulayan bu çıktıyı görürsünüz.
Bu örneği çalıştırmak ve tam ifade ağacını görmek için kaynak ifade ağacında bir değişiklik yaparsınız. İfade ağacı tüm sabitleri içerdiğinde, sonuçta elde edilen ağaç yalnızca 10
sabit değerini içerir. Derleyici tüm toplamayı gerçekleştirir ve ifadeyi en basit biçimine küçültür. İfadeye tek bir değişken eklemek, özgün ağacı görmek için yeterlidir:
Expression<Func<int, int>> sum = (a) => 1 + a + 3 + 4;
Bu toplam için bir ziyaretçi oluşturun ve bu çıkışı gördüğünüz ziyaretçiyi çalıştırın:
This expression is a/an Lambda expression type
The name of the lambda is <null>
The return type is System.Int32
The expression has 1 argument(s). They are:
This is an Parameter expression type
Type: System.Int32, Name: a, ByRef: False
The expression body is:
This binary expression is a Add expression
The Left argument is:
This binary expression is a Add expression
The Left argument is:
This binary expression is a Add expression
The Left argument is:
This is an Constant expression type
The type of the constant value is System.Int32
The value of the constant value is 1
The Right argument is:
This is an Parameter expression type
Type: System.Int32, Name: a, ByRef: False
The Right argument is:
This is an Constant expression type
The type of the constant value is System.Int32
The value of the constant value is 3
The Right argument is:
This is an Constant expression type
The type of the constant value is System.Int32
The value of the constant value is 4
Diğer örneklerden herhangi birini ziyaretçi kodu aracılığıyla çalıştırabilir ve hangi ağacı temsil ettiğinizi görebilirsiniz. Yukarıdaki sum3
ifadesinin bir örneği aşağıdadır (derleyicinin sabiti hesaplamasını önlemek için ek bir parametreyle):
Expression<Func<int, int, int>> sum3 = (a, b) => (1 + a) + (3 + b);
Ziyaretçinin çıktısı şu şekildedir:
This expression is a/an Lambda expression type
The name of the lambda is <null>
The return type is System.Int32
The expression has 2 argument(s). They are:
This is an Parameter expression type
Type: System.Int32, Name: a, ByRef: False
This is an Parameter expression type
Type: System.Int32, Name: b, ByRef: False
The expression body is:
This binary expression is a Add expression
The Left argument is:
This binary expression is a Add expression
The Left argument is:
This is an Constant expression type
The type of the constant value is System.Int32
The value of the constant value is 1
The Right argument is:
This is an Parameter expression type
Type: System.Int32, Name: a, ByRef: False
The Right argument is:
This binary expression is a Add expression
The Left argument is:
This is an Constant expression type
The type of the constant value is System.Int32
The value of the constant value is 3
The Right argument is:
This is an Parameter expression type
Type: System.Int32, Name: b, ByRef: False
Parantezlerin çıkışın bir parçası olmadığını fark edin. İfade ağacında giriş ifadesindeki parantezleri temsil eden düğüm yok. İfade ağacının yapısı, önceliği iletmek için gereken tüm bilgileri içerir.
Bu örneği genişletmek
Örnek yalnızca en temel ifade ağaçlarını ele alır. Bu bölümde gördüğünüz kod yalnızca sabit tamsayıları ve ikili +
işlecini işler. Son örnek olarak, ziyaretçiyi daha karmaşık bir ifadeyi işleyecek şekilde güncelleştirelim. Şimdi aşağıdaki faktöriyel ifade için çalışmasını sağlayalım:
Expression<Func<int, int>> factorial = (n) =>
n == 0 ?
1 :
Enumerable.Range(1, n).Aggregate((product, factor) => product * factor);
Bu kod, matematiksel faktöriyel işlevi için olası bir uygulamayı temsil eder. Bu kodu yazma şekliniz, İfadelere lambda ifadeleri atayarak ifade ağacı oluşturmanın iki sınırlamasını vurgular. İlk olarak, ifade lambdalarına izin verilmez. Başka bir deyişle C# dilinde ortak olan döngüleri, blokları, if / else deyimlerini ve diğer denetim yapılarını kullanamazsınız. İfadeleri kullanmakla sınırlısınız. İkincisi, özyinelemeli olarak aynı ifadeyi çağıramazsınız. Zaten bir temsilci olmuş olsaydı, yapabilirdiniz, ancak ifade ağacı biçiminde çağıramazsınız. ifade ağaçları oluşturmabölümünde bu sınırlamaların üstesinden gelmek için teknikler öğrenirsiniz.
Bu ifadede, tüm bu tür düğümlerle karşılaşırsınız:
- Eşittir (ikili ifade)
- Çarpma (ikili hesaplama ifadesi)
- Koşullu (
? :
ifadesi) - Metot Çağrı İfadesi (
Range()
veAggregate()
'in çağrılması)
Ziyaretçi algoritmasını değiştirmenin bir yolu, algoritmayı sürekli çalıştırmak ve her default
yan tümcesine ulaştığınızda düğüm türünü yazmaktır. Birkaç yinelemeden sonra olası düğümlerin her birini görürsünüz. O zaman ihtiyacınız olan her şey var. Sonuç aşağıdakine benzer olacaktır:
public static Visitor CreateFromExpression(Expression node) =>
node.NodeType switch
{
ExpressionType.Constant => new ConstantVisitor((ConstantExpression)node),
ExpressionType.Lambda => new LambdaVisitor((LambdaExpression)node),
ExpressionType.Parameter => new ParameterVisitor((ParameterExpression)node),
ExpressionType.Add => new BinaryVisitor((BinaryExpression)node),
ExpressionType.Equal => new BinaryVisitor((BinaryExpression)node),
ExpressionType.Multiply => new BinaryVisitor((BinaryExpression) node),
ExpressionType.Conditional => new ConditionalVisitor((ConditionalExpression) node),
ExpressionType.Call => new MethodCallVisitor((MethodCallExpression) node),
_ => throw new NotImplementedException($"Node not processed yet: {node.NodeType}"),
};
ConditionalVisitor
ve MethodCallVisitor
bu iki düğümü işler:
public class ConditionalVisitor : Visitor
{
private readonly ConditionalExpression node;
public ConditionalVisitor(ConditionalExpression node) : base(node)
{
this.node = node;
}
public override void Visit(string prefix)
{
Console.WriteLine($"{prefix}This expression is a {NodeType} expression");
var testVisitor = Visitor.CreateFromExpression(node.Test);
Console.WriteLine($"{prefix}The Test for this expression is:");
testVisitor.Visit(prefix + "\t");
var trueVisitor = Visitor.CreateFromExpression(node.IfTrue);
Console.WriteLine($"{prefix}The True clause for this expression is:");
trueVisitor.Visit(prefix + "\t");
var falseVisitor = Visitor.CreateFromExpression(node.IfFalse);
Console.WriteLine($"{prefix}The False clause for this expression is:");
falseVisitor.Visit(prefix + "\t");
}
}
public class MethodCallVisitor : Visitor
{
private readonly MethodCallExpression node;
public MethodCallVisitor(MethodCallExpression node) : base(node)
{
this.node = node;
}
public override void Visit(string prefix)
{
Console.WriteLine($"{prefix}This expression is a {NodeType} expression");
if (node.Object == null)
Console.WriteLine($"{prefix}This is a static method call");
else
{
Console.WriteLine($"{prefix}The receiver (this) is:");
var receiverVisitor = Visitor.CreateFromExpression(node.Object);
receiverVisitor.Visit(prefix + "\t");
}
var methodInfo = node.Method;
Console.WriteLine($"{prefix}The method name is {methodInfo.DeclaringType}.{methodInfo.Name}");
// There is more here, like generic arguments, and so on.
Console.WriteLine($"{prefix}The Arguments are:");
foreach (var arg in node.Arguments)
{
var argVisitor = Visitor.CreateFromExpression(arg);
argVisitor.Visit(prefix + "\t");
}
}
}
İfade ağacının çıktısı şöyle olacaktır:
This expression is a/an Lambda expression type
The name of the lambda is <null>
The return type is System.Int32
The expression has 1 argument(s). They are:
This is an Parameter expression type
Type: System.Int32, Name: n, ByRef: False
The expression body is:
This expression is a Conditional expression
The Test for this expression is:
This binary expression is a Equal expression
The Left argument is:
This is an Parameter expression type
Type: System.Int32, Name: n, ByRef: False
The Right argument is:
This is an Constant expression type
The type of the constant value is System.Int32
The value of the constant value is 0
The True clause for this expression is:
This is an Constant expression type
The type of the constant value is System.Int32
The value of the constant value is 1
The False clause for this expression is:
This expression is a Call expression
This is a static method call
The method name is System.Linq.Enumerable.Aggregate
The Arguments are:
This expression is a Call expression
This is a static method call
The method name is System.Linq.Enumerable.Range
The Arguments are:
This is an Constant expression type
The type of the constant value is System.Int32
The value of the constant value is 1
This is an Parameter expression type
Type: System.Int32, Name: n, ByRef: False
This expression is a Lambda expression type
The name of the lambda is <null>
The return type is System.Int32
The expression has 2 arguments. They are:
This is an Parameter expression type
Type: System.Int32, Name: product, ByRef: False
This is an Parameter expression type
Type: System.Int32, Name: factor, ByRef: False
The expression body is:
This binary expression is a Multiply expression
The Left argument is:
This is an Parameter expression type
Type: System.Int32, Name: product, ByRef: False
The Right argument is:
This is an Parameter expression type
Type: System.Int32, Name: factor, ByRef: False
Örnek Kitaplığı Genişletme
Bu bölümdeki örnekler, bir ifade ağacındaki düğümleri ziyaret etmek ve incelemek için temel teknikleri gösterir. Bir ifade ağacındaki düğümleri ziyaret etme ve bunlara erişmenin temel görevlerine odaklanmak için karşılaşabileceğiniz düğüm türlerini basitleştirdi.
İlk olarak, ziyaretçiler yalnızca tamsayı olan sabitleri işler. Sabit değerler başka bir sayısal tür olabilir ve C# dili bu türler arasındaki dönüştürmeleri ve yükseltmeleri destekler. Bu kodun daha sağlam bir sürümü tüm bu özellikleri yansıtır.
Son örnek bile olası düğüm türlerinin bir alt kümesini tanır. Yine de başarısız olmasına neden olan birçok ifadeyi besleyebilirsiniz. .NET Standard'da ExpressionVisitor adı altında tam bir uygulama bulunur ve tüm olası düğüm türlerini işleyebilir.
Son olarak, bu makalede kullanılan kitaplık tanıtım ve öğrenme için oluşturulmuştu. İyileştirilmiş değil. Yapıları netleştirir ve düğümleri ziyaret etmek ve orada ne olduğunu analiz etmek için kullanılan teknikleri vurgular.
Bu sınırlamalarla bile, ifade ağaçlarını okuyan ve anlayan algoritmalar yazma yolunda iyi olmanız gerekir.