當前位置: 妍妍網 > 碼農

C#中的Expression運算式樹

2024-04-27碼農

C#中的運算式樹

這節課來了解一下表示式樹是什麽?

在C#中,運算式樹是一種數據結構,它可以表示一些程式碼塊,如Lambda運算式或查詢運算式。運算式樹使你能夠檢視和運算元據,就像你可以檢視和操作程式碼一樣。它們通常用於建立動態查詢和解析運算式。

一、認識運算式樹

為什麽要這樣說?它和委托有什麽區別?

建立一個簡單的運算式樹和委托

public classExpressionDemo
{
voidShow()
{
Func<int, bool> fun1 = x => x > 10;
Expression<Func<int, bool>> expression1 = x => x > 10;
}
}

然後f12轉到定義

public sealed classExpression<TDelegate> : LambdaExpression

嘗試用大括弧定義一個運算式樹

debug執行後,用vs檢視一下定義的運算式樹物件.

發現運算式樹一些特點:

  1. 可以透過lambda運算式來聲明

  2. 是一個泛型類的介面,型別參數是一個委托

  3. Expression聲明中,不能包含大括弧.

  4. 透過VS展開檢視,包含body(lamubda的主體部份),描述了參數的名稱和型別,描述了返回值的名稱和型別;展開body, body包含 左邊是什麽,右邊是什麽,式子的操作型別是什麽.

結論:

運算式樹,是一個計算式的描述,按照常規的計算邏輯,透過類的內容來進行描述多個節點之間的關系;形似於一個樹形結構----二元樹;二元樹不斷地去分解,可以得到這個式子中的任何一個獨立的元素;----是一個二元樹,是一個數據結構;如果需要可以把這個結構不斷的拆解;得到中間的最小元素;在需要的時候,也可以透過每個元素,組裝起來;

委托是一個類,而運算式樹是一個二元樹的數據結構。

為了更加深入的了解運算式樹,這裏也使用ilspy進行反編譯,以便於更加了解運算式樹的本質.

這裏使用一個比較復雜的運算式樹的語句來方便我們去理解

Expression<Func<int ,int ,int>> expression2= (x, y) => x *y+2+3;

最佳化一下這段程式碼

//定義2個變量
ParameterExpression parameterExpression = Expression.Parameter(typeof(int), "x");
ParameterExpression parameterExpression2 = Expression.Parameter(typeof(int), "y");
//定義常量
var contact1 = Expression.Constant(2, typeof(int));
var contact2= Expression.Constant(3, typeof(int));
//定義運算式 x*y
var MultiplyXy= Expression.Multiply(parameterExpression, parameterExpression2);
//定義運算式 x*y的結果+2
var add1 = Expression.Add(MultiplyXy, contact1);
//定義運算式 x*y+2的結果+3
var add2 = Expression.Add(add1, contact2);
//定義最終的lambda運算式
Expression<Func<int, int, int>> expression2 = Expression.Lambda<Func<int, int, int>>(add2, new ParameterExpression[2]
{
parameterExpression,
parameterExpression2
});

如圖所示的解析:

已經將相應的程式碼貼上到上方,就是類似二元樹結構的因式分解,轉換成為最小的子問題,最後解決一個需要解決的大問題。

二、動態拼裝Expression

我們自己去拼裝一個運算式樹去理解運算式樹的秘密.

首先建立一個People類

public classPeople
{
public int Age { get; set; }
public string Name { get; set; }
public int Id;
}

下面來拼接一個比較復雜的運算式

Expression<Func<People, bool>> predicate = c => c.Id == 10 && c.Name.ToString().Equals("張三");

對應的運算式樹的程式碼

//定義一個People型別的參數
ParameterExpression parameterExpression = Expression.Parameter(typeof(People), "c");
//獲取People的Id內容
PropertyInfo? propertyId = typeof(People).GetProperty("Id");
//定義10這個常量
ConstantExpression constantExpression = Expression.Constant(10, typeof(int));
//定義c.Id>10這個運算式
BinaryExpression left =Expression.GreaterThan(Expression.Property(parameterExpression, propertyId), constantExpression);
//獲取People的Name內容
PropertyInfo? propertyName = typeof(People).GetProperty("Name");
//c.Name
MemberExpression memName = Expression.Property(parameterExpression, propertyName);
//to string方法
MethodInfo? methodtostring=typeof(string).GetMethod("ToString",new Type[0]);
//呼叫tostring方法
MethodCallExpression instance =Expression.Call(memName, methodtostring,Array.Empty<Expression>());
//獲取equals方法
MethodInfo? methodEquals = typeof(string).GetMethod("Equals", new Type[] { typeof(string) });
//定義c.Name.ToString().Equals("張三")這個運算式
MethodCallExpression right = Expression.Call(instance, methodEquals, Expression.Constant("張三", typeof(string)));
//定義c.Age<25這個運算式
PropertyInfo? propertyAge = typeof(People).GetProperty("Age");
ConstantExpression constantExpression2 = Expression.Constant(25, typeof(int));
BinaryExpression right2 = Expression.LessThan(Expression.Property(parameterExpression, propertyAge), constantExpression2);
//定義c.Id>10 && c.Name.ToString().Equals("張三") && c.Age<25這個運算式
BinaryExpression and1 = Expression.AndAlso(left, right);
BinaryExpression and2 = Expression.AndAlso(and1, right2);
//定義最終的lambda運算式
Expression<Func<People, bool>> expression = Expression.Lambda<Func<People, bool>>(and2, new ParameterExpression[1]
{
parameterExpression
});
//編譯運算式
Func<People, bool> func = expression.Compile();
//呼叫運算式
People people = new People()
{
Id = 11,
Name = "張三",
Age = 20
};
Console.WriteLine(func(people));

這樣就拼接出來了需要的運算式樹.

三、運算式樹的套用價值

為什麽要拼裝這個運算式目錄樹呢?

現在主流的是Linq:
Linq to Sql -----把相同的邏輯封裝,把不同的邏輯透過運算式目錄樹來傳遞;
傳遞運算式目錄樹:對應的是查詢條件;在傳遞之前就應該把查詢的條件拼裝好;

例子

Expression<Func<People, bool>> expression2 = p => p.Id == 10 && p.Name.Equals("陽光下的微笑");

拼接後的結果

//按關鍵字是否存在來拼裝;
Expression<Func<People, bool>> exp = p=> true;
Console.WriteLine("使用者輸入個名稱,為空就跳過");
string name = Console.ReadLine();
if (!string.IsNullOrWhiteSpace(name))
{
//exp = p => p.Name.Contains(name);
exp= exp.And(c=>c.Name.Contains(name));
}
Console.WriteLine("使用者輸入個最小年紀,為空就跳過");
string age = Console.ReadLine();
if (!string.IsNullOrWhiteSpace(age) && int.TryParse(age, out int iAge))
{
// exp = p => p.Age > iAge;
exp = exp.And(p => p.Age > iAge);
}

例子2

//Expression<Func<People, bool>> newExpress = x => x.Age > 5 && x.Id > 5

現在使用運算式樹進行連結

Expression<Func<People, bool>> lambda1 = x => x.Age > 5;
Expression<Func<People, bool>> lambda2 = x => x.Id > 5;
//Expression<Func<People, bool>> newExpress = x => x.Age > 5 && x.Id > 5;
Expression<Func<People, bool>> lambda3 = lambda1.And(lambda2); //且 兩個都滿足,透過&&連結
Expression<Func<People, bool>> lambda4 = lambda1.Or(lambda2);//或 兩個只要有一個就可以 透過或者來連結 ||
Expression<Func<People, bool>> lambda5 = lambda1.Not();//非

這裏實作了常見的且、或、非邏輯運算子的運算式

public static classExpressionExtend
{
/// <summary>
/// 合並運算式 expr1 AND expr2
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="expr1"></param>
/// <param name="expr2"></param>
/// <returns></returns>
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
{
//return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(expr1.Body, expr2.Body), expr1.Parameters);
ParameterExpression newParameter = Expression.Parameter(typeof(T), "c");
NewExpressionVisitor visitor = new NewExpressionVisitor(newParameter);
var left = visitor.Replace(expr1.Body);
var right = visitor.Replace(expr2.Body); //為了能夠生成一個新的運算式目錄樹
var body = Expression.And(left, right);
return Expression.Lambda<Func<T, bool>>(body, newParameter);
}
/// <summary>
/// 合並運算式 expr1 or expr2
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="expr1"></param>
/// <param name="expr2"></param>
/// <returns></returns>
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
{
ParameterExpression newParameter = Expression.Parameter(typeof(T), "c");
NewExpressionVisitor visitor = new NewExpressionVisitor(newParameter);
var left = visitor.Replace(expr1.Body);
var right = visitor.Replace(expr2.Body);
var body = Expression.Or(left, right);
return Expression.Lambda<Func<T, bool>>(body, newParameter);
}
public static Expression<Func<T, bool>> Not<T>(this Expression<Func<T, bool>> expr)
{
var candidateExpr = expr.Parameters[0];
var body = Expression.Not(expr.Body);
return Expression.Lambda<Func<T, bool>>(body, candidateExpr);
}
}
internal classNewExpressionVisitor : ExpressionVisitor
{
public ParameterExpression _NewParameter { get; private set; }
public NewExpressionVisitor(ParameterExpression param)
{
this._NewParameter = param;
}
public Expression Replace(Expression exp)
{
return this.Visit(exp);
}
protected override Expression VisitParameter(ParameterExpression node)
{
return this._NewParameter;
}
}





現在有一個新的需求,需要把People拷貝到NewPeople這個新的類,來看下效率怎麽樣?

People和PeopleCopy類

public classPeople
{
public int Age { get; set; }
public string Name { get; set; }
public int Id;
}
/// <summary>
/// 實體類Target
/// PeopleDTO
/// </summary>
public classPeopleCopy
{
public int Age { get; set; }
public string Name { get; set; }
public int Id;
}

直接賦值的方式

PeopleCopy peopleCopy1 = new PeopleCopy()
{
Id = people.Id,
Name = people.Name,
Age = people.Age
};

反射賦值的方式

public classReflectionMapper
{
/// <summary>
/// 反射
/// </summary>
/// <typeparam name="TIn"></typeparam>
/// <typeparam name="TOut"></typeparam>
/// <param name="tIn"></param>
/// <returns></returns>
public static TOut Trans<TIn, TOut>(TIn tIn)
{
TOut tOut = Activator.CreateInstance<TOut>();
foreach (var itemOut in tOut.GetType().GetProperties())
{
var propName = tIn.GetType().GetProperty(itemOut.Name);
itemOut.SetValue(tOut, propName.GetValue(tIn));
}
foreach (var itemOut in tOut.GetType().GetFields())
{
var fieldName = tIn.GetType().GetField(itemOut.Name);
itemOut.SetValue(tOut, fieldName.GetValue(tIn));
}
return tOut;
}
}
PeopleCopy peopleCopy2= ReflectionMapper.Trans<People, PeopleCopy>(people);

json序列化的方式

public classSerializeMapper
{
/// <summary>
/// 序列化反序列化方式
/// </summary>
/// <typeparam name="TIn"></typeparam>
/// <typeparam name="TOut"></typeparam>
public static TOut Trans<TIn, TOut>(TIn tIn)
{
string strTin = JsonConvert.SerializeObject(tIn);
return JsonConvert.DeserializeObject<TOut>(strTin);
}
}
PeopleCopy peopleCopy3 = SerializeMapper.Trans<People, PeopleCopy>(people);

運算式目錄樹的方式


public classExpressionMapper
{
/// <summary>
/// 字典緩存--hash分布
/// </summary>
private static Dictionary<string, object> _Dic = new Dictionary<string, object>();
/// <summary>
/// 字典緩存運算式樹
/// </summary>
/// <typeparam name="TIn"></typeparam>
/// <typeparam name="TOut"></typeparam>
/// <param name="tIn"></param>
/// <returns></returns>
public static TOut Trans<TIn, TOut>(TIn tIn)
{
string key = string.Format("funckey_{0}_{1}", typeof(TIn).FullName, typeof(TOut).FullName);
if (!_Dic.ContainsKey(key))
{
#region 這裏是拼裝---賦內容值的程式碼
ParameterExpression parameterExpression = Expression.Parameter(typeof(TIn), "p");
//MemberBinding: 就是一個運算式目錄樹
List<MemberBinding> memberBindingList = new List<MemberBinding>();
foreach (var item in typeof(TOut).GetProperties()) //這裏是處理內容的
{
MemberExpression property = Expression.Property(parameterExpression, typeof(TIn).GetProperty(item.Name));
MemberBinding memberBinding = Expression.Bind(item, property);
memberBindingList.Add(memberBinding);
}
foreach (var item in typeof(TOut).GetFields()) //處理欄位的
{
MemberExpression property = Expression.Field(parameterExpression, typeof(TIn).GetField(item.Name));
MemberBinding memberBinding = Expression.Bind(item, property);
memberBindingList.Add(memberBinding);
}
MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(typeof(TOut)), memberBindingList.ToArray()); //組裝了一個轉換的過程;
Expression<Func<TIn, TOut>> lambda = Expression.Lambda<Func<TIn, TOut>>(memberInitExpression, new ParameterExpression[]
{
parameterExpression
});
#endregion

Func<TIn, TOut> func = lambda.Compile();//拼裝是免洗的
_Dic[key] = func;
}
return ((Func<TIn, TOut>)_Dic[key]).Invoke(tIn);
}
}
PeopleCopy peopleCopy4 = ExpressionMapper.Trans<People, PeopleCopy>(people);




運算式+反射+泛型類的方式

public classExpressionGenericMapper<TIn, TOut>//Mapper`2
{
private static Func<TIn, TOut> _FUNC = null;
staticExpressionGenericMapper()
{
ParameterExpression parameterExpression = Expression.Parameter(typeof(TIn), "p");
List<MemberBinding> memberBindingList = new List<MemberBinding>();
foreach (var item in typeof(TOut).GetProperties())
{
MemberExpression property = Expression.Property(parameterExpression, typeof(TIn).GetProperty(item.Name));
MemberBinding memberBinding = Expression.Bind(item, property);
memberBindingList.Add(memberBinding);
}
foreach (var item in typeof(TOut).GetFields())
{
MemberExpression property = Expression.Field(parameterExpression, typeof(TIn).GetField(item.Name));
MemberBinding memberBinding = Expression.Bind(item, property);
memberBindingList.Add(memberBinding);
}
MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(typeof(TOut)), memberBindingList.ToArray());
Expression<Func<TIn, TOut>> lambda = Expression.Lambda<Func<TIn, TOut>>(memberInitExpression, new ParameterExpression[]
{
parameterExpression
});
_FUNC = lambda.Compile();//拼裝是免洗的
}
public static TOut Trans(TIn t)
{
return _FUNC(t);
}
}
}
PeopleCopy peopleCopy5 = ExpressionGenericMapper<People, PeopleCopy>.Trans(people);

最後執行一百萬次,來看一下效率。

{
People people = new People()
{
Id = 11,
Name = "Richard",
Age = 31
};
long common = 0;
long generic = 0;
long cache = 0;
long reflection = 0;
long serialize = 0;
{
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = 0; i < 1_000_000; i++)
{
PeopleCopy peopleCopy = new PeopleCopy()
{
Id = people.Id,
Name = people.Name,
Age = people.Age
};
}
watch.Stop();
common = watch.ElapsedMilliseconds;
}
{
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = 0; i < 1_000_000; i++)
{
PeopleCopy peopleCopy = ReflectionMapper.Trans<People, PeopleCopy>(people);
}
watch.Stop();
reflection = watch.ElapsedMilliseconds;
}
{
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = 0; i < 1_000_000; i++)
{
PeopleCopy peopleCopy = SerializeMapper.Trans<People, PeopleCopy>(people);
}
watch.Stop();
serialize = watch.ElapsedMilliseconds;
}
{
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = 0; i < 1_000_000; i++)
{
PeopleCopy peopleCopy = ExpressionMapper.Trans<People, PeopleCopy>(people);
}
watch.Stop();
cache = watch.ElapsedMilliseconds;
}
{
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = 0; i < 1_000_000; i++)
{
PeopleCopy peopleCopy = ExpressionGenericMapper<People, PeopleCopy>.Trans(people);
}
watch.Stop();
generic = watch.ElapsedMilliseconds;
}
Console.WriteLine($"common = {common} ms"); //效能最高,但是不能通用;
Console.WriteLine($"reflection = {reflection} ms");
Console.WriteLine($"serialize = {serialize} ms");
Console.WriteLine($"cache = {cache} ms");
Console.WriteLine($"generic = {generic} ms"); //效能好,而且擴充套件性也好===又要馬兒跑,又要馬兒不吃草。。。
}

看執行後的結果

核心:動態生成寫死;----程式碼執行的時候生成了一段新的邏輯;

四、運算式樹和sql

為什麽要使用運算式目錄樹來拼裝解析呢?
可以提供重用性

如果封裝好一個方法,接受一個運算式樹,在解析的時候,其實就是不斷的存取,存取的時候,會按照固定的規則,避免出錯;

任何的一個運算式樹都可以用一個通用的方法解析並且支持泛型,更加容易去封裝;

例子:

需要的擴充套件類

public classOperationsVisitor : ExpressionVisitor
{
public Expression Modify(Expression expression)
{
Console.WriteLine(expression.ToString()) ;
//ExpressionVisitor:
//1.Visit方法--存取運算式目錄樹的入口---分辨是什麽型別的運算式目錄
//2.排程到更加專業的方法中進一步存取,存取一遍之後,生成一個新的運算式目錄 ---有點像遞迴,不全是遞迴;
//3.因為運算式目錄樹是個二元樹,ExpressionVisitor一直往下存取,一直到葉節點;那就存取了所有的節點
//4.在存取的任何一個環節,都可以拿到對應當前環節的內容(參數名稱、參數值。。),就可以進一步擴充套件
return this.Visit(expression);
}
/// <summary>
/// 覆寫父類方法
/// </summary>
/// <param name="b"></param>
/// <returns></returns>
protected override Expression VisitBinary(BinaryExpression b)
{
if (b.NodeType == ExpressionType.Add)
{
Expression left = this.Visit(b.Left);
Expression right = this.Visit(b.Right);
return Expression.Subtract(left, right);
}
elseif (b.NodeType==ExpressionType.Multiply) //如果是相乘
{
Expression left = this.Visit(b.Left);
Expression right = this.Visit(b.Right);
return Expression.Divide(left, right); //相除
}
return base.VisitBinary(b);
}
/// <summary>
/// 覆寫父類方法
/// </summary>
/// <param name="node"></param>
/// <returns></returns>
protected override Expression VisitConstant(ConstantExpression node)
{
return base.VisitConstant(node);
}


對應的運算式解析

Expression<Func<int, int, int>> exp = (m, n) => m * n + 2;
OperationsVisitor visitor = new OperationsVisitor();
//visitor.Visit(exp);
Expression expNew = visitor.Modify(exp);

同時運算式樹中已經透過使用觀察者模式封裝好了Visit方法.

  1. Visit方法--存取運算式樹的入口---分辨是什麽型別的運算式目錄

  2. 排程到更加專業的方法中進一步存取,存取一邊以後,生成一個新的運算式目錄. --- 有點像遞迴,不全是遞迴

  3. 因為運算式目錄樹是一個二元樹,ExpreesionVistor一直往下存取,一直到葉子節點;透過二元樹的遍歷就存取了所有的節點.

  4. 在存取的任何一個環節,都可以拿到對應當前環節的內容(參數名稱、參數值...)就可以進一步擴充套件.

現在開始將運算式樹跟sql語句進行連線

例子:
擴充套件類

public classConditionBuilderVisitor : ExpressionVisitor
{
private Stack<string> _StringStack = new Stack<string>();
public stringCondition()
{
string condition = string.Concat(this._StringStack.ToArray());
this._StringStack.Clear();
return condition;
}
/// <summary>
/// 如果是二元運算式
/// </summary>
/// <param name="node"></param>
/// <returns></returns>
protected override Expression VisitBinary(BinaryExpression node)
{
if (node == null) throw new ArgumentNullException("BinaryExpression");
this._StringStack.Push(")");
base.Visit(node.Right);//解析右邊
this._StringStack.Push(" " + node.NodeType.ToSqlOperator() + " ");
base.Visit(node.Left);//解析左邊
this._StringStack.Push("(");
return node;
}
/// <summary>
/// 解析內容
/// </summary>
/// <param name="node"></param>
/// <returns></returns>
protected override Expression VisitMember(MemberExpression node)
{
if (node == null) throw new ArgumentNullException("MemberExpression");
//this._StringStack.Push(" [" + node.Member.Name + "] ");
////return node;
if (node.Expression is ConstantExpression)
{
var value1 = this.InvokeValue(node);
var value2 = this.ReflectionValue(node);
//this.ConditionStack.Push($"'{value1}'");
this._StringStack.Push("'" + value2 + "'");
}
else
{
this._StringStack.Push(" [" + node.Member.Name + "] ");
}
return node;
}

private object InvokeValue(MemberExpression member)
{
var objExp = Expression.Convert(member, typeof(object));//struct需要
return Expression.Lambda<Func<object>>(objExp).Compile().Invoke();
}
private object ReflectionValue(MemberExpression member)
{
var obj = (member.Expression as ConstantExpression).Value;
return (member.Member as FieldInfo).GetValue(obj);
}
/// <summary>
/// 常量運算式
/// </summary>
/// <param name="node"></param>
/// <returns></returns>
protected override Expression VisitConstant(ConstantExpression node)
{
if (node == null) throw new ArgumentNullException("ConstantExpression");
this._StringStack.Push(" '" + node.Value + "' ");
return node;
}
/// <summary>
/// 方法運算式
/// </summary>
/// <param name="m"></param>
/// <returns></returns>
protected override Expression VisitMethodCall(MethodCallExpression m)
{
if (m == null) throw new ArgumentNullException("MethodCallExpression");
string format;
switch (m.Method.Name)
{
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(m.NodeType + " is not supported!");
}
this.Visit(m.Object);
this.Visit(m.Arguments[0]);
string right = this._StringStack.Pop();
string left = this._StringStack.Pop();
this._StringStack.Push(String.Format(format, left, right));
return m;
}
}











對應的sql語句的解析

{
Expression<Func<People, bool>> lambda = x => x.Age > 5 && x.Id > 5
&& x.Name.StartsWith("1") // like '1%'
&& x.Name.EndsWith("1") // like '%1'
&& x.Name.Contains("1");// like '%1%'
string sql = string.Format("Delete From [{0}] WHERE [Age]>5 AND [ID] >5"
, typeof(People).Name
, " [Age]>5 AND [ID] >5");
ConditionBuilderVisitor vistor = new ConditionBuilderVisitor();
vistor.Visit(lambda);
Console.WriteLine(vistor.Condition());
}
{
// ((( [Age] > '5') AND( [Name] = [name] )) OR( [Id] > '5' ))
string name = "AAA";
Expression<Func<People, bool>> lambda = x => x.Age > 5 && x.Name == name || x.Id > 5;
ConditionBuilderVisitor vistor = new ConditionBuilderVisitor();
vistor.Visit(lambda);
Console.WriteLine(vistor.Condition());
}
{
Expression<Func<People, bool>> lambda = x => x.Age > 5 || (x.Name == "A" && x.Id > 5);
ConditionBuilderVisitor vistor = new ConditionBuilderVisitor();
vistor.Visit(lambda);
Console.WriteLine(vistor.Condition());
}
{
Expression<Func<People, bool>> lambda = x => (x.Age > 5 || x.Name == "A") && x.Id > 5;
ConditionBuilderVisitor vistor = new ConditionBuilderVisitor();
vistor.Visit(lambda);
Console.WriteLine(vistor.Condition());
}

在我自己的看法,使用運算式樹而不是傳統的方式去解析sql語句的優點

  1. 透過二元樹的方式表達,更加的有條理性

  2. 使用泛型等技術更方式實作一個通用的sql語句的解析。

  3. 會有型別檢查,出錯後也能使用例外處理。

-End-

了解.NET/C#頂階技術,可以加入下述技術討論群。 向大佬學習、探行業內幕、享時代機遇。 進名企+拿高新!

點選上方卡片關註公眾號

回復'面試',獲取C#/.NET/.NET Core面試寶典

回復'C#',領取零基礎學習C#編程

回復'NET',領取.NET零基礎入門到實戰

回復'Linux',領取Linux從入門到精通

回復'wpf',領取高薪熱門【WPF上位機+工業互聯網】從零手寫實戰

回復'Modbus',領取初識C#+上位機Modbus通訊

回復'PLC',領取C#語言與西門子PLC的通訊實操

回復'blazor',領取blazor從入門到實戰

回復'TypeScript',領取前端熱門TypeScript系統教程

回復'vue',領取vue前端從入門到精通

回復'23P',領取C#實作23種常見設計模式

回復'MongoDB',領取MongoDB實戰

回復'Trans',領取分布式事務

回復'Lock',領取分布式鎖實踐

回復'Docker',領取微服務+Docker綜合實戰

回復'K8s',領取K8s部署微服務

回復'加群',進.NET技術社群交流群