最近要重寫公司自己開發的ORM框架;其中有一部分就是查詢的動態表達式;於是對這方面的東西做了一個簡單的梳理
官網的解釋:
表達式樹以樹形數據結構表示代碼,其中每一個節點都是一種表達式,比如方法調用和 x < y 這樣的二元運算等。
你可以對表達式樹中的代碼進行編輯和運算。 這樣能夠動態修改可執行代碼、在不同數據庫中執行 LINQ 查詢以及創建動態查詢。
表達式樹還能用於動態語言運行時 (DLR) 以提供動態語言和 .NET 之間的互操作性,同時保證編譯器編寫員能夠發射表達式樹而非 Microsoft 中間語言 (MSIL)
總結:構造可執行的代碼,以樹形結構的方式構造;
表達式與委托:委托可以直接執行,而表達式不可以,表達式可以編譯成委托
備注:實際上.NET還有以文本的方式構造可執行代碼塊的方式,但和表達式樹還是有區別
我個人工作中主要在以下場景中使用表達式樹:
1,代理類方法的構造(如AOP,gRpc,WebService中),通過表達式樹,構造委托執行要代理的方法
2,EF,MongoDB,自定義ORM等數據訪問層過濾使用的Expression,最典型的就是針對IQueryable類型分頁,排序的動態擴展
3,常用的Linq中AND,OR等Predicate加強的一些擴展
4,數學表達式,偽SQL代碼的執行解析器的構造(這個比較底層)
Expression調試工具:2019下載地址ExpressionTreeVisualizer
Expression調試工具源碼:https://github.com/zspitz/ExpressionTreeVisualizer/blob/master/README.md
Expression類有以下屬性:
Body:表達式的主體(類似於方法體)
Parameters:Lambda表達示的參數(參數列表)
NodeType:得到樹中某些節點的表達式類型(ExpressionType),這是一個有45種不同值的枚舉類型,代表表達式節點的所有可能類型,如返回常數、
也可能返回參數、或者返回一個值是否小於另外一個(<),或者返回一個值是否大於另外一個(>),或者返回兩個值的和( )等等。
Type:得到表達式的靜態類型
表達式樹的幾個比較常用的類型/方法,.NET FrameWork與.NET Core在表達式樹的使用上做了一些調整
Expression:表達式樹核心類,基類;提供了Call,Add等構建Expression的靜態方法
Expression.Call():非常核心的一個方法,用於創建調用類的方法的方法類型表達式樹;在.NET Core(易用)和.NET FrameWork(易懂)做了些變化
Expression.Parameter():用於創建參數類型表達式樹
Expression.Constant():用於創建常量類型表達式樹
Expression<TDelegate>:強類型的表達式樹類
Expression.Lambda<TDelegate>:將Expression轉換成Lambda表達式(設置Lambda的方法體和參數列表)
LambdaExpression.Parameters:做表達式樹拼接時常用到
LambdaExpression.tailCall:屬性,標記是否【尾調優化,參考后面】
創建表達式樹的方式(以下示例為.Net Core3.1)
1,以單行Lambda表達式創建表達式樹( 包含方法體的Lambda表達式是不能創建表達式樹 );
這種表達式樹的構造方式,Lambda表達式實際上是給LambdaExpression的Body(Body為Expression類型)賦值
//使用單行Lambda創建表達式 static void Expression1() { //表達式樹的Body Expression<Func<int, int>> expression = a => a + 100; Expression<Func<int, int, int>> expression2 = (a, b) => a + b; Console.WriteLine(expression.ToString()); Console.WriteLine(expression.Compile()(120)); }
如果你使用具有方法體的Lambda表達式創建表達式樹,是不成功的;表達式樹是Lambda表達式的內存中表現形式
//錯誤示例:使用具有方法體的Lambda創建表達式
static void Expression2()
{
//編譯不通過,不支持有方法體的Lambda創建表達式
//Expression<Action<int>> expression1 = a => { };
//編譯不通過,有方法體的Lambda創建表達式
//Expression<Func<int, int>> expression2 = a => { return a + 100; };
//Console.WriteLine(expression1.ToString());
}
2,通過API創建表達式樹
創建沒有入參和返回參數的表達式樹
//無入參,返參的表達式
static void Expression3()
{
var WriteLine = typeof(Console).GetMethod("WriteLine", new[] { typeof(string) });
//表達式樹的Body
var method = Expression.Block(Expression.Call(null, WriteLine, Expression.Constant("來自Expression的輸出")));
var action = Expression.Lambda<Action>(method).Compile();
action();
}
創建有入參,沒有返回參數的表達式樹
//有入參,無返參的表達式
static void Expression4()
{
var stringParam = Expression.Parameter(typeof(string), "stringParam");
var WriteLine = typeof(Console).GetMethod("WriteLine", new[] { typeof(string) });
var method = Expression.Block(Expression.Call(null, WriteLine, new[] { stringParam }));
var action = Expression.Lambda<Action<string>>(method, new[] { stringParam }).Compile();
action("來自Expression4的輸出yyyyyy");
}
創建有入參,有返回參數的表達式樹
//有入參,有返參的表達式1
static void Expression5()
{
var stringParam = Expression.Parameter(typeof(string), "stringParam");
var WriteLine = typeof(Console).GetMethod("WriteLine", new[] { typeof(string) });
var method = Expression.Block(
Expression.Call(null, WriteLine, new[] { stringParam }),
Expression.Assign(stringParam, Expression.Constant("你好啊"))
);
var action = Expression.Lambda<Func<string, string>>(method, new[] { stringParam }).Compile();
var ms = action("Expression5ss");
}
//有入參,有返參的表達式2
static void Expression6()
{
var stringParam = Expression.Parameter(typeof(string), "stringParam");
var method = Expression.Block(Expression.Call(null,typeof(string).GetMethod("Concat", new[] { typeof(string), typeof(string) }), new Expression[] { stringParam, Expression.Constant("哈哈哈哈") }));
var action = Expression.Lambda<Func<string, string>>(method, new[] { stringParam }).Compile();
var ms = action("test單個參數");
Console.WriteLine(ms);
}
//有入參,有返參的表達式3
static void Expression7()
{
var stringParam = Expression.Parameter(typeof(string), "stringParam");
var stringParam2 = Expression.Parameter(typeof(string), "stringParam2");
var method = Expression.Block(
new[] { stringParam2 },
Expression.Assign(stringParam2, Expression.Constant("參數2")),
Expression.Call(null, typeof(string).GetMethod("Concat", new[] { typeof(string), typeof(string) }), new Expression[] { stringParam, stringParam2 })
);
var action = Expression.Lambda<Func<string, string>>(method, new[] { stringParam }).Compile();
var ms = action("test單個參數,且Call聲明參數");
Console.WriteLine(ms);
}
//有入參,有返參的表達式4
static void Expression8()
{
var stringParam = Expression.Parameter(typeof(string), "stringParam");
var stringParam2 = Expression.Parameter(typeof(string), "stringParam2");
var method = Expression.Block(
Expression.Call(null, typeof(string).GetMethod("Concat", new[] { typeof(string), typeof(string) }), new Expression[] { stringParam, stringParam2 })
);
var action = Expression.Lambda<Func<string, string,string>>(method, new[] { stringParam, stringParam2 }).Compile();
var ms = action("多個參數:參數1","啦啦啦啦");
Console.WriteLine(ms);
}
函數尾部調用參數優化:簡稱尾調優化
1,當函數的最后一步時調用其他函數時,即為尾調;尾調不只是說函數的最尾部;示例,Update,Add方法都是尾部調用
public int AddUser(string id,string name) {
if (id==null) {
return Update(name);
}
return Add(name);
}
2,LambdaExpression.tailCall屬性和Expresssion.Lambda方法中的tailCall參數都是用來標記是否需要尾調優化的,
如果標記尾調優化,則表示當執行進入尾調的函數時,當前函數已經不再依賴父函數上的數據,父函數上的所占用的內存空間將被清空,用於優化節省內存
表達式樹動態復制對象:
/// <summary>
/// 當前類
/// </summary>
public class Student
{
public int Id { get; set; }
public string UserName { get; set; }
public string Name { get; set; }
}
/// <summary>
/// 目標類
/// </summary>
public class StudentCopy
{
public int Id { get; set; }
public string UserName { get; set; }
public string Name { get; set; }
}
class Program
{
static void Main(string[] args)
{
Student student = new Student()
{
Id = 1002,
Name = "哈哈哈",
UserName = "cc"
};
//使用new
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
var entity1 = CopyToByNew(student);
stopwatch.Stop();
Console.WriteLine($"new:{stopwatch.ElapsedTicks}");
//使用反射
stopwatch = new Stopwatch();
stopwatch.Start();
var entity2 = CopyToByReflect1<Student, StudentCopy>(student);
stopwatch.Stop();
Console.WriteLine($"reflect:{stopwatch.ElapsedTicks}");
stopwatch = new Stopwatch();
stopwatch.Start();
var entity3 = CopyToByReflect2<Student, StudentCopy>(student);
stopwatch.Stop();
Console.WriteLine($"reflect:{stopwatch.ElapsedTicks}");
//使用json
stopwatch = new Stopwatch();
stopwatch.Start();
var entity4 = CopyToByJson<Student, StudentCopy>(student);
stopwatch.Stop();
Console.WriteLine($"json:{stopwatch.ElapsedTicks}");
//使用表達式構造委托
stopwatch = new Stopwatch();
stopwatch.Start();
var entity5 = CopyToByExpression<Student, StudentCopy>(student);
stopwatch.Stop();
Console.WriteLine($"expression:{stopwatch.ElapsedTicks}");
Console.WriteLine("Hello World!");
Console.ReadKey();
}
static StudentCopy CopyToByNew(Student student)
{
return new StudentCopy
{
Id = student.Id,
Name = student.Name,
UserName = student.UserName
};
}
static Tout CopyToByReflect1<Tin, Tout>(Tin dto)
{
Tout entity = Activator.CreateInstance<Tout>();
foreach (var itemOut in entity.GetType().GetProperties())
{
var propIn = dto.GetType().GetProperty(itemOut.Name);
if (propIn != null)
{
itemOut.SetValue(entity, propIn.GetValue(dto));
}
}
return entity;
}
static Tout CopyToByReflect2<Tin, Tout>(Tin dto)
{
Tout entity = Activator.CreateInstance<Tout>();
foreach (var itemOut in entity.GetType().GetFields())
{
var propIn = dto.GetType().GetField(itemOut.Name);
itemOut.SetValue(entity, propIn.GetValue(dto));
}
return entity;
}
static Tout CopyToByJson<TIn, Tout>(TIn dto)
{
return JsonConvert.DeserializeObject<Tout>(JsonConvert.SerializeObject(dto));
}
static Tout CopyToByExpression<Tin, Tout>(Tin dto)
{
ParameterExpression parameterExpression = Expression.Parameter(typeof(Tin), "a");
List<MemberBinding> memberBindings = 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);
memberBindings.Add(memberBinding);
}
MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(typeof(Tout)), memberBindings.ToArray());
Expression<Func<Tin, Tout>> lambda = Expression.Lambda<Func<Tin, Tout>>(memberInitExpression,
new ParameterExpression[] { parameterExpression });
Func<Tin, Tout> func = lambda.Compile();
return func(dto);
}
}

使用ExpressionTreeVisualizer工具
下載地址:參考前面的地址
ExpressionTreeVisualizer工具主要有三個功能
- 表達式樹結構的可視化
- 表達式樹源碼顯示
- 表達式樹節點結構: parameters, closure variables, constants and default values

