SoFunction
Updated on 2025-03-06

Detailed explanation of C# expression directory tree example

1. Expression directory tree

The expression directory tree is defined by Expression in C#. It is a syntax tree, or a data structure. It is mainly used to store a structure that requires calculation and calculation. It only provides storage functions and does not perform calculations. Usually Expression is used with Lambda, which can be an anonymous method. Expression can be created dynamically.

Declare a lambda expression, which can indicate the type or be an anonymous method:

//Func<int, int, int> func = new Func<int, int, int>((m, n) => m * n + 2);
Func<int, int, int> func = (m, n) => m * n + 2;

The above code can be defined using Expression:

Expression&lt;Func&lt;int, int, int&gt;&gt; exp = (m, n) =&gt; m * n + 2;// Lambda expression declaration expression directory tree

The Expression method body can only be a whole and cannot have curly braces. The following code is not allowed:

Expression&lt;Func&lt;int, int, int&gt;&gt; exp1 = (m, n) =&gt;//The method body can only be one{
 return m * n + 2;
};

The above func and exp execution results are the same:

int iResult1 = (3, 2);
int iResult2 = ().Invoke(3, 2);

2. Build an expression directory tree

The above expression example can be constructed from the main through Expression, defining m and n as ParameterExpression parameters, and defining 2 as constant expression ConstantExpression. Using the static method of Expression, representing multiplication and addition:

ParameterExpression parameterLeft = (typeof(int), "m");//Define parametersParameterExpression parameterRight = (typeof(int), "n");//Define parametersBinaryExpression binaryMultiply = (parameterLeft, parameterRight);//The first step of multiplicationConstantExpression constant = (2, typeof(int)); //Define constant parametersBinaryExpression binaryAdd = (binaryMultiply, constant);//Construct the second step of additionvar expression = &lt;Func&lt;int, int, int&gt;&gt;(binaryAdd, parameterLeft, parameterRight);//Build expressionvar func = (); //Compiled as lambda expressionint iResult3 = func(3, 2);
int iResult4 = ().Invoke(3, 2);
int iResult5 = ()(3, 2);

The independent construction Expression is that the definition of the parameter name may not be m or n, but may be other a, b or x or y.

How to build a complex expression directory tree? More methods, properties, extension methods, etc. in Expression need to be used. First define a class:

public class People
{
 public int Age { get; set; }
 public string Name { get; set; }
 public int Id;
}

Based on the above class, build expressions: Expression<Func<People, bool>> lambda = x => ().Equals("5");

In this example, the ToString() method of int is used, and the Equals method of string is also used. The construction process is as follows:

//The following expression directory tree implements the expression of lambdaExpression&lt;Func&lt;People, bool&gt;&gt; lambda = x =&gt; ().Equals("5");
//Declare a parameter objectParameterExpression parameterExpression = (typeof(People), "x");
//Look for fields and bind methods to access parameter object fields (properties):MemberExpression member = (parameterExpression, typeof(People).GetField("Id"));
//This can be used instead ofvar temp =(parameterExpression, "Id");
//Calling the ToString method of the field: ()MethodCallExpression method = (member, typeof(int).GetMethod("ToString", new Type[] { }), new Expression[0]);
//Calling the Equals method of the string: ().Equals("5")MethodCallExpression methodEquals = (method, typeof(string).GetMethod("Equals", new Type[] { typeof(string) }), new Expression[]
{
 ("5", typeof(string))//Compare with constants, it can also be a parameter});
//Create a directory tree expressionar expression = &lt;Func&lt;People, bool&gt;&gt;(methodEquals, new ParameterExpression[] {parameterExpression });
bool bResult = ().Invoke(new People()
{
 Id = 5,
 Name = "Nigle",
 Age = 31
});

3. Use Expression to map attributes with the same name of different objects

The People class was built before, and now we build a new PeopleCopy class:

public class PeopleCopy
{
 public int Age { get; set; }
 public string Name { get; set; }
 public int Id;
}

Now declare a People object, and then copy the data of the People object to the PeopleCopy new object, and directly hard-code it:

1. Hard coded

People people = new People()
{
 Id = 11,
 Name = "Nigle",
 Age = 31
};
PeopleCopy peopleCopy = new PeopleCopy()
{
 Id = ,
 Name = ,
 Age = 
};

If you write this way, for classes with more attributes or fields, we need to write many assignments when copying, and the code will be very long. At this point, what we can think of is copying through reflection:

2. Reflection copy

public static TOut Trans<TIn, TOut>(TIn tIn)
{
 TOut tOut = <TOut>();
 foreach (var itemOut in ().GetProperties())
 {
 foreach (var itemIn in ().GetProperties())
 {
  if (())
  {
  (tOut, (tIn));
  break;
  }
 }
 }
 foreach (var itemOut in ().GetFields())
 {
 foreach (var itemIn in ().GetFields())
 {
  if (())
  {
  (tOut, (tIn));
  break;
  }
 }
 }
 return tOut;
}

Through reflection, we can find the properties and fields corresponding to the original type by outputting the attributes or fields of the type, then obtain the values ​​and set the values ​​to make the assignment copy. In addition, what we can think of is the serialization method of deep cloning to deserialize data:

3. Serialization and deserialization

public class SerializeMapper
{
 /// <summary>Serialization deserialization method/summary> public static TOut Trans&lt;TIn, TOut&gt;(TIn tIn)
 {
 //Json serialization is used, and other serialization methods can also be used. return &lt;TOut&gt;((tIn));
 }
}

The previous three methods are the most commonly used methods, but the expression directory tree introduced in this article has not been used. How to combine expression directory tree with copy? There are two ways [Cache + Expression Directory], [Generics + Expression Directory]

4. Cache + Expression Directory

/// &lt;summary&gt;
/// Generate expression directory tree cache/// &lt;/summary&gt;
public class ExpressionMapper
{
 private static Dictionary&lt;string, object&gt; _Dic = new Dictionary&lt;string, object&gt;();

 /// &lt;summary&gt;
 /// Dictionary cache expression tree /// &lt;/summary&gt;
 public static TOut Trans&lt;TIn, TOut&gt;(TIn tIn)
 {
 string key = ("funckey_{0}_{1}", typeof(TIn).FullName, typeof(TOut).FullName);
 if (!_Dic.ContainsKey(key))
 {
  ParameterExpression parameterExpression = (typeof(TIn), "p");
  List&lt;MemberBinding&gt; memberBindingList = new List&lt;MemberBinding&gt;();
  foreach (var item in typeof(TOut).GetProperties())
  {
  MemberExpression property = (parameterExpression, typeof(TIn).GetProperty());
  //Bind the relationship between Out and In: Age =  MemberBinding memberBinding = (item, property);
  (memberBinding);
  }
  foreach (var item in typeof(TOut).GetFields())
  {
  MemberExpression property = (parameterExpression, typeof(TIn).GetField());
  MemberBinding memberBinding = (item, property);
  (memberBinding);
  }
  MemberInitExpression memberInitExpression = ((typeof(TOut)), ());
  Expression&lt;Func&lt;TIn, TOut&gt;&gt; lambda = &lt;Func&lt;TIn, TOut&gt;&gt;(memberInitExpression, parameterExpression);
  Func&lt;TIn, TOut&gt; func = ();//The assembly is a one-time  _Dic[key] = func;
 }
 return ((Func&lt;TIn, TOut&gt;)_Dic[key]).Invoke(tIn);
 }
}

5. Generics + Expression Directory

/// &lt;summary&gt;
/// Generate expression directory tree Generic cache/// &lt;/summary&gt;
/// &lt;typeparam name="TIn"&gt;&lt;/typeparam&gt;
/// &lt;typeparam name="TOut"&gt;&lt;/typeparam&gt;
public class ExpressionGenericMapper&lt;TIn, TOut&gt;//Mapper`2
{
 private static Func&lt;TIn, TOut&gt; func = null;
 static ExpressionGenericMapper()
 {
 ParameterExpression parameterExpression = (typeof(TIn), "p");
 List&lt;MemberBinding&gt; memberBindingList = new List&lt;MemberBinding&gt;();
 foreach (var item in typeof(TOut).GetProperties())
 {
  MemberExpression property = (parameterExpression, typeof(TIn).GetProperty());
  MemberBinding memberBinding = (item, property);
  (memberBinding);
 }
 foreach (var item in typeof(TOut).GetFields())
 {
  MemberExpression property = (parameterExpression, typeof(TIn).GetField());
  MemberBinding memberBinding = (item, property);
  (memberBinding);
 }
 MemberInitExpression memberInitExpression = ((typeof(TOut)), ());
 Expression&lt;Func&lt;TIn, TOut&gt;&gt; lambda = &lt;Func&lt;TIn, TOut&gt;&gt;(memberInitExpression, new ParameterExpression[]
 {
  parameterExpression
 });
 func = ();//The assembly is a one-time }
 public static TOut Trans(TIn t)
 {
 return func(t);
 }
}

In addition to the above 5 methods, you can also use the AutoMapper that comes with the framework. First, we need to add a reference to AutoMapper to use it directly. The specific code is:

6. AutoMapper

public class AutoMapperTest
{
 public static TOut Trans<TIn, TOut>(TIn tIn)
 {
 return <TOut>(tIn);
 }
}

Evaluation: Evaluate the above 6 methods, each copy method runs 100 0000 times:

Stopwatch watch = new Stopwatch();
();
for (int i = 0; i &lt; 1000000; i++)
{
 //Test six methods PeopleCopy peopleCopy = new PeopleCopy() {Id = , Name = ,Age = }; //Copy by direct assignment-22ms //PeopleCopy peopleCopy = <People, PeopleCopy>(people); //Reflection assignment copy---1573ms //PeopleCopy peopleCopy = <People, PeopleCopy>(people); //Serialization method---2716ms //PeopleCopy peopleCopy = <People, PeopleCopy>(people); //Expression directory tree cache Copy---517ms //PeopleCopy peopleCopy = ExpressionGenericMapper<People, PeopleCopy>.Trans(people); //Expression directory tree Generic cache--77ms //PeopleCopy peopleCopy = &lt;People, PeopleCopy&gt;(people);    //AutoMapper---260ms
}
();
($"time consuming:{ } ms");

4. SQL selection construction of expression directory tree

When building conditional statements, traditional SQL needs to make many judgments to build a complete query statement.

People p = new People()
{
 Id = 11,
 Name = "Nigle",
 Age = 31
};
//How to assemble sqlstring sql = "SELECT * FROM USER WHERE Id=1";
if (())
{
 sql += $" and name like '%{}%'";
}
sql += $" and age &gt;{}";

In fact, occasionally we use linq queries or lambda expressions for conditional filtering, such as var lambda = x => > 5; In fact, we can build the above Expression:

People p = new People()
{
 Id = 11,
 Name = "Nigle",
 Age = 31
};
//Stand the expression directory tree and hand it over to the lower end for useParameterExpression parameterExpression = (typeof(People), "x");//Declare a parameterExpression propertyExpression = (parameterExpression, typeof(People).GetProperty("Age"));// Object that declares access parameter attributes//Expression property = (parameterExpression, typeof(People).GetField("Id"));
ConstantExpression constantExpression = (5, typeof(int));//Declare a constantBinaryExpression binary = (propertyExpression, constantExpression);//Add comparison methodvar lambda = &lt;Func&lt;People, bool&gt;&gt;(binary, new ParameterExpression[] { parameterExpression });//Construct the expression bodybool bResult = ().Invoke(p); //Compare values

5. Modify the expression directory tree

This example will modify the addition of the built expression directory tree to subtraction. To modify, splice and read nodes, you need to use the ExpressionVisitor class. The ExpressionVisitor class can dynamically decouple and read related nodes and methods.

Visit (Expression node) in the ExpressionVisitor class is the entrance to interpret expressions, and then it can magically distinguish parameters and method bodies, then schedule the expression to a more dedicated access method in this class, and then parse it layer by layer until the final leaf node!

First write the OperationsVisitor class to modify:

internal class OperationsVisitor : ExpressionVisitor
{
 public Expression Modify(Expression expression)
 {
 return (expression);
 }
 protected override Expression VisitBinary(BinaryExpression b)
 {
 if ( == )
 {
  Expression left = ();
  Expression right = ();
  return (left, right);
 }

 return (b);
 }
 protected override Expression VisitConstant(ConstantExpression node)
 {
 return (node);
 }
}

Then, write a lambda expression, modify it and calculate the result:

//Modify the expression directory treeExpression&lt;Func&lt;int, int, int&gt;&gt; exp = (m, n) =&gt; m * n + 2;
OperationsVisitor visitor = new OperationsVisitor();
Expression expNew = (exp);

int? iResult = (expNew as Expression&lt;Func&lt;int, int, int&gt;&gt;)?.Compile().Invoke(2, 3);

Visit method can be identified that m*n+2 is a binary tree, and it will be parsed step by step through the following figure. If m*n is encountered, it will directly call VisitBinary(BinaryExpression b) method. If m or n is encountered, VisitParameter(ParameterExpression node) method will be called. If 2 constants are encountered, VisitConstant(ConstantExpression node) will be called.

The relationship between ORM and expression tree directory:

EF is often used, but they are actually inheriting Queryable. Then the EF we use usually uses var items = ().Where(x => == || == 0) , where actually passes on the expression directory tree. The where and other lambda expressions written by EF are reverse parsed through the ExpressionVisitor class! A parsing method that simulates EF will be constructed later.

6. Construct the expression directory tree analysis that simulates EF

First, the method to build a parsing expression directory tree cannot use the default one.

/// &lt;summary&gt;
/// Visitors in the expression directory tree/// &lt;/summary&gt;
internal class ConditionBuilderVisitor : ExpressionVisitor
{
 /// &lt;summary&gt;
 /// Used to store data such as conditions /// &lt;/summary&gt;
 private Stack&lt;string&gt; _StringStack = new Stack&lt;string&gt;(); 
 /// &lt;summary&gt;
 /// 
 /// &lt;/summary&gt;
 /// &lt;returns&gt;&lt;/returns&gt;
 internal string Condition()
 {
 string condition = (this._StringStack.ToArray());
 this._StringStack.Clear();
 return condition;
 }
 /// &lt;summary&gt;
 /// If it is a binary expression /// &lt;/summary&gt;
 /// &lt;param name="node"&gt;&lt;/param&gt;
 /// &lt;returns&gt;&lt;/returns&gt;
 protected override Expression VisitBinary(BinaryExpression node)
 {
 if (node == null) throw new ArgumentNullException("BinaryExpression");

 this._StringStack.Push(")");
 ();//Parse the right side this._StringStack.Push(" " + ToSqlOperator() + " ");
 ();//Parse the left side this._StringStack.Push("(");

 return node;
 }
 /// &lt;summary&gt;
 /// 
 /// &lt;/summary&gt;
 /// &lt;param name="node"&gt;&lt;/param&gt;
 /// &lt;returns&gt;&lt;/returns&gt;
 protected override Expression VisitMember(MemberExpression node)
 {
 if (node == null)
  throw new ArgumentNullException("MemberExpression");
 this._StringStack.Push(" [" +  + "] ");
 return node;
 return (node);
 }
 /// &lt;summary&gt;
 /// Operator that converts node type to Sql /// &lt;/summary&gt;
 /// &lt;param name="type"&gt;&lt;/param&gt;
 /// &lt;returns&gt;&lt;/returns&gt;
 string ToSqlOperator(ExpressionType type)
 {
 switch (type)
 {
  case ():
  case ():
  return "AND";
  case ():
  case ():
  return "OR";
  case ():
  return "NOT";
  case ():
  return "&lt;&gt;";
  case :
  return "&gt;";
  case :
  return "&gt;=";
  case :
  return "&lt;";
  case :
  return "&lt;=";
  case ():
  return "=";
  default:
  throw new Exception("This method is not supported");
 }
 }
 /// &lt;summary&gt;
 /// Constant expression /// &lt;/summary&gt;
 /// &lt;param name="node"&gt;&lt;/param&gt;
 /// &lt;returns&gt;&lt;/returns&gt;
 protected override Expression VisitConstant(ConstantExpression node)
 {
 if (node == null)
  throw new ArgumentNullException("ConstantExpression");
 this._StringStack.Push(" '" +  + "' ");
 return node;
 }
 /// &lt;summary&gt;
 /// Method expression /// &lt;/summary&gt;
 /// &lt;param name="m"&gt;&lt;/param&gt;
 /// &lt;returns&gt;&lt;/returns&gt;
 protected override Expression VisitMethodCall(MethodCallExpression m)
 {
 if (m == null) throw new ArgumentNullException("MethodCallExpression");

 string format;
 switch ()
 {
  case "StartsWith":
  format = "({0} LIKE {1}+'%')";
  break;

  case "Contains":
  format = "({0} LIKE '%'+{1}+'%')";
  break;

  case "EndsWith":
  format = "({0} LIKE '%'+{1})";
  break;

  default:
  throw new NotSupportedException( + " is not supported!");
 }
 ();
 ([0]);
 string right = this._StringStack.Pop();
 string left = this._StringStack.Pop();
 this._StringStack.Push((format, left, right));
 return m;
 }
}

Then, the external can write the query conditions of the expression directory tree, and then parse it into the corresponding SQL statement through the instance of this class:

{
 Expression&lt;Func&lt;People, bool&gt;&gt; lambda = x =&gt;  &gt; 5 &amp;&amp;  &gt; 5
      &amp;&amp; ("1")
      &amp;&amp; ("1")
      &amp;&amp; ("2");
 //" x => > 5 && > 5" is equivalent to a sql statement string sql = ("Delete From [{0}] WHERE {1}", typeof(People).Name, " [Age]&gt;5 AND [ID] &gt;5");
 ConditionBuilderVisitor vistor = new ConditionBuilderVisitor();
 (lambda);
 (());
}
{
 Expression&lt;Func&lt;People, bool&gt;&gt; lambda = x =&gt;  &gt; 5 &amp;&amp;  == "A" ||  &gt; 5;
 ConditionBuilderVisitor vistor = new ConditionBuilderVisitor();
 (lambda);
 (());
}
{
 Expression&lt;Func&lt;People, bool&gt;&gt; lambda = x =&gt;  &gt; 5 || ( == "A" &amp;&amp;  &gt; 5);
 ConditionBuilderVisitor vistor = new ConditionBuilderVisitor();
 (lambda);
 (());
}
{
 Expression&lt;Func&lt;People, bool&gt;&gt; lambda = x =&gt; ( &gt; 5 ||  == "A") &amp;&amp;  &gt; 5;
 ConditionBuilderVisitor vistor = new ConditionBuilderVisitor();
 (lambda);
 (());
}

7. Connect expression directory tree

In addition to modifying the expression directory tree, we can also splice two or more expression directory trees together by splicing the expression directory tree. First write a new NewExpressionVisitor, inherited from ExpressionVisitor, and is called when splicing. It is an internal class placed inside ExpressionExtend accessing the spliced ​​class. Then write the corresponding extension method: Add, Or, Not

/// &lt;summary&gt;
/// Merge expressions And Or Not extension/// &lt;/summary&gt;
public static class ExpressionExtend
{
 /// <summary>Merge expressions expLeft and expRight</summary> public static Expression&lt;Func&lt;T, bool&gt;&gt; And&lt;T&gt;(this Expression&lt;Func&lt;T,bool&gt;&gt; expLeft,Expression&lt;Func&lt;T,bool&gt;&gt; expRight)
 {
 // Used to replace the parameter name, the parameters of the two are different ParameterExpression newParameter = (typeof(T), "c");
 NewExpressionVisitor visitor = new NewExpressionVisitor(newParameter);
 //You need to replace the parameters with the same ones first, the parameter names may be different var left = ();//The expression on the left var right = ();//The expression on the right var body = (left, right);//Merge expression return &lt;Func&lt;T, bool&gt;&gt;(body, newParameter);
 }
 /// <summary>Merge expressions expr1 or expr2</summary> public static Expression&lt;Func&lt;T, bool&gt;&gt; Or&lt;T&gt;(this Expression&lt;Func&lt;T, bool&gt;&gt; expr1, Expression&lt;Func&lt;T, bool&gt;&gt; expr2)
 {

 ParameterExpression newParameter = (typeof(T), "c");
 NewExpressionVisitor visitor = new NewExpressionVisitor(newParameter);
 //You need to replace the parameters with the same ones first, the parameter names may be different var left = ();
 var right = ();
 var body = (left, right);
 return &lt;Func&lt;T, bool&gt;&gt;(body, newParameter);
 }
 public static Expression&lt;Func&lt;T, bool&gt;&gt; Not&lt;T&gt;(this Expression&lt;Func&lt;T, bool&gt;&gt; expr)
 {
 var candidateExpr = [0];
 var body = ();
 return &lt;Func&lt;T, bool&gt;&gt;(body, candidateExpr);
 }
 /// <summary>parameter replacement </summary> class NewExpressionVisitor : ExpressionVisitor
 {
 public ParameterExpression _NewParameter { get; private set; }
 public NewExpressionVisitor(ParameterExpression param)
 {
  this._NewParameter = param;// Used to replace the parameters }
 /// <summary> Replace</summary> public Expression Replace(Expression exp)
 {
  return (exp);
 }
 protected override Expression VisitParameter(ParameterExpression node)
 {
  //Return new parameter name  return this._NewParameter;
 }
 }
}

Here is the test code:

Expression<Func<People, bool>> lambda1 = x =>  > 5;
Expression<Func<People, bool>> lambda2 = p =>  > 5;
Expression<Func<People, bool>> lambda3 = (lambda2);
Expression<Func<People, bool>> lambda4 = (lambda2);
Expression<Func<People, bool>> lambda5 = ();

List<People> people = new List<People>()
{
 new People(){Id=4,Name="123",Age=4},
 new People(){Id=5,Name="234",Age=5},
 new People(){Id=6,Name="345",Age=6},
};

List<People> lst1 = (()).ToList();
List<People> lst2 = (()).ToList();
List<People> lst3 = (()).ToList();

Summarize

This is all about this article about the C# expression directory tree. For more related contents of the C# expression directory tree, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!