日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > C# >内容正文

C#

【C#语法糖 Lambda】2 - 表达式树与LINQ

發布時間:2025/3/8 C# 27 如意码农
生活随笔 收集整理的這篇文章主要介紹了 【C#语法糖 Lambda】2 - 表达式树与LINQ 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一、聲明

  曾經遇到一個這樣的場景:

GetEntities(a => a.OrderKindCodeId == 16 && a.IsDeleted, this.DefaultContext)
protected IEnumerable<TEntity> GetEntities(Expression<Func<TDto, bool>> expression, IRepositoryContext context)

  我就很奇怪,明明a => a.OrderKindCodeId == 16 && a.IsDeleted是Lambda表達式,為什么入參是表示式樹呢?一次偶然調試中發現,原來C#支持表達式樹的聲明像Lambda一樣寫(當然有一定局限)。

  上述報錯,是編譯器無法推斷弱類型是表達式樹類型還是Lambda類型,需要指定類型:

Func<string, string, bool> lambda = (a,b) => a.StartsWith(b);
Expression<Func<string, string, bool>> e = (a, b) => a.StartsWith(b); var expressionToLambda = e.Compile(); // 將表達式樹轉化成lambda表達式,故類型與e相同,但內容與e有區別
var typeIsEqual = expressionToLambda.GetType() == lambda.GetType(); // true
bool isEqual = lambda.Equals(expressionToLambda); // false

  不同的原因應該是.Compile()的鍋,此處不研究。

  換一種方法聲明表達式樹:

static Func<string, bool> buildlambda(Expression<Func<string, bool>> exp1, Expression<Func<string, bool>> exp2)
{
ParameterExpression parameter = Expression.Parameter(typeof(string));
Expression left = exp1.Body;
Expression right = exp2.Body;
BinaryExpression AndAlso = Expression.AndAlso(left, right);
return Expression.Lambda<Func<string, bool>>(AndAlso, parameter).Compile();
}

  看起來很正常,但運行時compile會報錯:

System.InvalidOperationException:“從作用域“”引用了“System.String”類型的變量“a”,但該變量未定義”

  運算過程出錯,因為找不到參數——啥意思?上例的錯誤其實相當于這樣:

Expression<Func<string, string, bool>> e = (a, b) => a.StartsWith(b);
var e1 = Expression.Lambda(e.Body);
bool c1 = e == e1; // false

e1 = Expression.Lambda(e.Body, e.Parameters); // e1是Expression<T>類型,指定了參數,讓運算有個目標
var e2 = e1 as Expression; // e2是LambdaExpression類型
c1 = e == e1; // false,類型不同

二、存儲結構

  表達式樹是有穩定性的,它由一個一個節點掛成一棵樹,那怎么修改它?動一棵樹,那必然是要遍歷的,要遞歸。

  問題又來了,構建一個表達式樹如此費勁(不使用lambda表達式下)干嘛要用它——表達式樹存在的意義?

三、意義與應用

  3 - 1 為什么需要表達式樹

  表達式樹的應用,最后還是要.Compile()成lambda表達式,那為什么不直接用lambda?

  ⑴ 調試可見方法如何實現

  ⑵ Lambda表達式能轉換成表達式樹,然后由其他代碼處理,從而達到在不同執行環境中執行等價的行為。

  Linq To SQL :將源語言(比如C#)生成一個表達式樹,其結果作為一個中間格式,再將其轉換成目標平臺上的本地語言(比如SQL)。

  那么這個中間格式直接傳lambda表達式行不行?

  測試了一下,數據庫響應速度特別慢,可能還會發生超時。僅就這個原因,也必須用表達式樹了:

  看用時:

  3 - 2 ExpressionVisitor

  上述提及與數據庫交互,那就涉及節點值問題(例如節點是一個函數的值,我們希望直接傳函數結果而不是傳函數),盡量處理得干凈一些,這就需要修改(/重構)樹,可以使用ExpressionVisitor類。

  ExpressionVisitor應該是一個抽象類,程序員按需求繼承重寫。

  這個類主要處理好Parameter和各個節點類型,入口函數應該是Visit(Expression exp),此處僅列出一部分:

        public virtual Expression Visit(Expression exp)
{
if (exp == null)
return exp;
switch (exp.NodeType)
{
case ExpressionType.And: // +
case ExpressionType.AndAlso: // 并且
case ExpressionType.LessThan: // 小于
case ExpressionType.GreaterThan: // 大于
case ExpressionType.Equal: // 等于
case ExpressionType.NotEqual: // 不等于
return this.VisitBinary((BinaryExpression)exp);
case ExpressionType.Constant: // 常數
return this.VisitConstant((ConstantExpression)exp);
case ExpressionType.Parameter: // 參數
return this.VisitParameter((ParameterExpression)exp);
case ExpressionType.MemberAccess: //從字段或屬性進行讀取的運算,如 obj.SampleProperty
return this.VisitMemberAccess((MemberExpression)exp);
case ExpressionType.Call: // 方法調用
return this.VisitMethodCall((MethodCallExpression)exp);
case ExpressionType.Lambda: // Lambda表達式
return this.VisitLambda((LambdaExpression)exp);
default:
throw new Exception(string.Format("Unhandled expression type: '{0}'", exp.NodeType));
}
}

  應用上例,改動一下:

  ExpressionVisitorTest:

    public class ExpressionVisitorTest
{
public ExpressionVisitorTest() { } public Expression Visit(Expression exp)
{
if (exp == null)
return exp;
switch (exp.NodeType)
{
case ExpressionType.Parameter:
return VisitParameter((ParameterExpression)exp);
case ExpressionType.NotEqual:
return this.VisitBinary((BinaryExpression)exp);
case ExpressionType.MemberAccess:
return this.VisitMemberAccess((MemberExpression)exp);
case ExpressionType.Constant:
return this.VisitConstant((ConstantExpression)exp);
default: return exp;
}
} protected virtual Expression VisitBinary(BinaryExpression b)
{
Expression left = this.Visit(b.Left);
Expression right = this.Visit(b.Right);
Expression conversion = this.Visit(b.Conversion);
if (left != b.Left || right != b.Right || conversion != b.Conversion)
{
if (b.NodeType == ExpressionType.Coalesce && b.Conversion != null)
return Expression.Coalesce(left, right, conversion as LambdaExpression);
else
return Expression.MakeBinary(b.NodeType, left, right, b.IsLiftedToNull, b.Method);
}
return b;
} public Expression VisitMemberAccess(MemberExpression exp)
{
Expression body = Visit(exp.Expression);
if (body != exp.Expression)
return Expression.MakeMemberAccess(exp, exp.Member);
return exp;
} public Expression VisitParameter(ParameterExpression exp)
{
return Parameter;
} protected virtual Expression VisitConstant(ConstantExpression c)
{
return c;
} public ParameterExpression Parameter
{
get;
set;
}
}

  先看看要啥效果:

   ExpressionVisitor visitor = new ExpressionVisitorInstance();
string testStr = true ? "0" : "123";
Expression<Func<string, bool>> aa = a => a != testStr;
var bb = visitor.Visit(aa);
isequal = bb.Equals(aa); // false

  (下載ExpressionVisitor.cs)

  例如重寫VisitMemberAccess方法:

    public class ExpressionVisitorInstance : ExpressionVisitor
{
protected override Expression VisitMemberAccess(MemberExpression m)
{
Expression expr = base.VisitMemberAccess(m);
return SimplyMemberAccess(expr);
} private static Expression SimplyMemberAccess(Expression expr)
{
MemberExpression me = expr as MemberExpression;
if (me == null) return expr; object target;
if (me.Expression == null)
target = null;
else if (me.Expression is ConstantExpression)
target = (me.Expression as ConstantExpression).Value;
else
return expr; return Expression.Constant(me.Member.GetValue(target));
}
}

  MemberExpression的屬性:

MemberExpression

CanReduce

能否化簡

Expression

值表達式

Member

Reflection.RtFieldInfo類型,可處理此處獲得真實值

NodeType

節點類型

Type

值類型

  處理反射的幫助類:

    public static class ReflectionHelper
{
public static object GetValue(this MemberInfo member, object component)
{
if (component == null && !member.CanGetFromNull()) return null; if (member is PropertyInfo)
return ((PropertyInfo)member).GetValue(component, null);
if (member is FieldInfo)
return ((FieldInfo)member).GetValue(component); MethodInfo method = member as MethodInfo;
if (method != null && typeof(void) != method.ReturnType && method.GetParameters().Length == 0)
{
return method.Invoke(component, null);
} return null;
} private static bool CanGetFromNull(this MemberInfo member)
{
if (member is PropertyInfo)
return ((PropertyInfo)member).GetGetMethod().IsStatic;
else if (member is FieldInfo)
return ((FieldInfo)member).IsStatic;
else if (member is MethodBase)
return ((MethodBase)member).IsStatic;
else
return false;
}
}

  3 - 3 表達式樹的其他作用:

  不同類型數字相加,詳見 http://mng.bz/9m8i:

        public static T Add<T>(T a, T b)
{
ParameterExpression left = Expression.Parameter(typeof(T), "a"),
right = Expression.Parameter(typeof(T), "b");
Expression Add = Expression.Add(left, right); Func<T, T, T> add = Expression.Lambda<Func<T, T, T>>(Add, left, right).Compile();
return add(a, b);
}

  但略感到尷尬的是,現在使用的.net 4.6.1已經能支持類型推斷了,會推斷一個“合適的類型”,也即意味著不用人工去強轉類型了:

    int d1 = 1;
double d2 = 2.1;
float d4 = 5.5f;
long d5 = (long)10;
var d3 = d1+ d2 + d5 + d4; // 存在從int到double的隱式轉換

參考:

[0] 深入C# 第三版

[1] https://www.cnblogs.com/FlyEdward/archive/2010/12/06/Linq_ExpressionTree7.html

[2] https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/expression-trees/how-to-modify-expression-trees

總結

以上是生活随笔為你收集整理的【C#语法糖 Lambda】2 - 表达式树与LINQ的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。