首页 > 编程语言 >C#的λ表达式树(LambdaExpression)保姆级超详细简单入门教程

C#的λ表达式树(LambdaExpression)保姆级超详细简单入门教程

时间:2022-12-28 15:37:31浏览次数:60  
标签:C# 入门教程 LambdaExpression typeof var lambda Expression 表达式 Lambda

有看过我之前发表过的C#相关文章分享和阅读过我代码的朋友们可能会在我的代码里面经常看到各种各样的λ表达式动态拼接,C#的λ表达式树是一个好东西,也是别的语言学不来的,熟悉掌握λ表达式就能够实现各种linq场景的个性化操作,如动态拼接查询条件、排序方式等,也能够实现替代反射的高性能操作,比如我们常用到的IQueryable和IEnumerable,每个扩展方法就全是λ表达式树。

之前也分享过几篇关于λ表达式的文章,但鉴于还是有一部分初学者对于λ表达式树不太懂的,而网络上关于λ表达式树的科普不是很全面,今天就来填一下这个坑,分享一下λ表达式树的简单入门。

鉴于还有些伙伴不太清楚λ表达式树和委托的区别的,在教程正式开始之前,先科普下λ表达式树、lambda、委托它们之间的区别和联系吧。

λ表达式树、lambda、委托它们之间的区别和联系

1. 委托是一种类型,是方法的抽象,通过委托可以将方法以参数的形式传递给另一个方法,同时调用委托的时候,它缩包含的方法都会被实现。委托的关键字是delegate,可以自定义委托,也可以使用内置委托,通过简化,可以将Lambda表达式或Lambda语句赋值给委托,委托的调用包括同步调用和异步调用。

2. 表达式树(Expression)是一种数据结构,表达式树也称表达式目录树,是将代码以一种抽象的方式表示成一个对象树,树中每个节点本身都是一个表达式。表达式树不是可执行代码,它是一种数据结构。可以利用Lambda表达式进行声明,Lambda表达式的规则要符合Expression中Func委托的参数规则,但Lambda语句是不能声明的。

3. lambda是当委托只有一句话代码的时候的最简写形式。

4. Lambda表达式不仅可以用来创建委托实例,C#编译器也能够将他们转换成表达式树。

//Func委托,必须要有返回值,最后一个参数为返回值,前面为输入参数
Func<int, int, int> func1 = new Func<int, int, int>((int m, int n) =>
{
    return m * n + 2;
});

//对其进行最简化(Lambda语句)
Func<int, int, int> func2 = (m, n) =>
{
    return m * n + 2;
};

//对其进行最简化(Lambda表达式)
Func<int, int, int> func3 = (m, n) => m * n + 2;

//调用委托
int result1 = func1.Invoke(2, 3);
int result2 = func2.Invoke(2, 3);
int result3 = func3.Invoke(2, 3);
Console.WriteLine("委托三种形式结果分别为:{0},{1},{2}", result1, result2, result3);

λ表达式树入门

λ表达式树也是代码,我们把它当成另外一种动态语言学习就好了,它也有常量、参数、运算、判断等操作,λ表达式树最终的本质就是一个方法的编译状态,如:x=>x+1这个最基本的表达式,它对应的等效方法就是:

int Inc(int x){
    return x+1;
}

我们分析x=>x+1表达式的每一个成分:

x:参数x;
=>:{}花括号;
+:add操作;
1:常量;

那为什么叫表达式树呢?

x=>x+1它的每一个成分都是一个节点,每个节点之间进行关联,得到最终的表达式,故称之为表达式树。如图所示:

再比如:(a,b)=>a*3+b*4,可视化结构如下:

通过Expression类构造一个最基本的λ表达式树

简单介绍之后,我们就开始实战吧,我们从最简单的开始,构造上面两个基本例子:

x=>x+1

var x=Expression.Parameter(typeof(int), "x");//声明一个int类型的参数x
var num1=Expression.Constant(1);//声明一个常量1
var inc=Expression.Add(x, num1); //执行加法操作
var lambda=Expression.Lambda<Func<int,int>>(inc,x);// 将表达式树组装成lambda表达式树,一个输入,一个输出,故Func<int,int>
var func=lambda.Compile(); //将lambda表达式树编译成Func委托

一个最基本的表达式树编译完成,我们输出看一下长什么样子吧,并调用func(10)看看得到什么结果:

(a,b)=>a*3+b*4

看上去比上面的例子复杂了一点,但我们把它拆分成两个基本的表达式处理就好了,按照上面的图例,我们从下往上进行构建:

先组合a*3,再组合b*4,最后再将a*3和b*4进行两两组合即可完成。

var a = Expression.Parameter(typeof(int), "a");//声明一个int类型的参数a
var num1 = Expression.Constant(3);//声明一个常量3
var r1 = Expression.Multiply(a, num1);//乘法表达式,a*3
var b = Expression.Parameter(typeof(int), "b");//声明一个int类型的参数b
var num2 = Expression.Constant(4);//声明一个常量4
var r2 = Expression.Multiply(b, num2);//乘法表达式,b*4
var add = Expression.Add(r1, r2);//a*3+b*4
var lambda = Expression.Lambda<Func<int, int, int>>(add, a, b);// 将表达式树组装成lambda表达式树,两个输入,一个输出,故Func<int, int, int>
var func = lambda.Compile(); //将lambda表达式树编译成Func委托

我们再来构造一个更复杂的表达式:

(a,b,c,d,e)=>((a+b)*(c-d))%e

有点蒙了吧,我们还是拆成最基本的两两组合就好了:

var a = Expression.Parameter(typeof(double), "a");
var b = Expression.Parameter(typeof(double), "b");
var r1 = Expression.Add(a, b);//a+b
var c = Expression.Parameter(typeof(double), "c");
var d = Expression.Parameter(typeof(double), "d");
var r2 = Expression.Subtract(c, d);//c-d
var r3 = Expression.Multiply(r1, r2);//(a+b)*(c-d)
var e = Expression.Parameter(typeof(double), "e");
var r4 = Expression.Modulo(r3, e);//(a+b)*(c-d)%e
var lambda = Expression.Lambda<Func<double, double, double, double, double, double>>(r4, a, b, c, d, e);

感觉这玩意儿有点意思了吧!接下来继续深入研究一些基本操作。

测试类

为方便接下来的示范,我们先新建一个class:

    public class MyClass
    {
        public MyClass(int num)
        {
            Number = num;
        }
        public bool MyBool { get; set; }
        public int Number { get; set; }
        public string MyProperty { get; set; }
        public List<string> List { get; set; }
        public string SayHello(string s)
                {
                    return s + DateTime.Now.ToString("yyyy-MM-dd");
                }
    }

替代反射

创建实例new MyClass(100)

Expression.New操作可为class创建实例对象,比如我想通过λ表达式树实现var myClass = new MyClass(100); 其可视化结构如下:

代码实现:

var newExpression = Expression.New(typeof(MyClass).GetConstructor(new[] { typeof(int) }), Expression.Constant(100)); // 有参构造函数方式
//var newExpression = Expression.New(typeof(MyClass)); //无参构造函数方式
var lambda = Expression.Lambda<Func<MyClass>>(newExpression);
var myClass = lambda.Compile()();

获取属性值e=>e.MyProperty

操作对象的属性我们需要用到Expression.Property,e=>e.MyProperty表达式分析得到两部分:参数和属性,也是一种基本表达式

Expression.Parameter可得到参数e;Expression.Property可得到属性MyProperty:

var e = Expression.Parameter(typeof(MyClass), "e");
var property = Expression.Property(e, nameof(MyClass.MyProperty));
var lambda = Expression.Lambda<Func<MyClass, string>>(property, e);

为属性赋值e=>e.List=new List<string>(){"sdf"}

结合上面的属性操作,既然能获取属性,那就一定能设置属性值,Expression.Assign操作便是赋值操作

var newExpression = Expression.New(typeof(MyClass).GetConstructor(new[] { typeof(int) }), Expression.Constant(100)); // 有参构造函数方式
var lambda = Expression.Lambda<Func<MyClass>>(newExpression);
var myClass = lambda.Compile()();
var e = Expression.Parameter(typeof(MyClass), "e");
var property = Expression.Property(e, nameof(MyClass.MyProperty));
var assignString = Expression.Assign(property, Expression.Constant("sss"));//为字符串类型的属性赋值
var assignList = Expression.Assign(Expression.Property(e, "List"), Expression.Constant(new List<string>() { "sdf" }));//为集合类型的属性赋值
Expression.Lambda<Action<MyClass>>(assignList, e).Compile()(myClass);
Expression.Lambda<Action<MyClass>>(assignString, e).Compile()(myClass);// 因为赋值操作没有返回值,所以是Action,并且只有一个入参

调用方法e=>e.GetHashCode();

调用方法通过Expression.Call进行对象的方法调用,调用前需要先获取被调用的方法对象

var e = Expression.Parameter(typeof(MyClass), "e");
var method = typeof(MyClass).GetMethod(nameof(GetHashCode));//获取MyClass的GetHashCode方法
var call = Expression.Call(e, method);//e.GetHashCode()
var lambda = Expression.Lambda<Func<MyClass, int>>(call, e);

调用有参方法e=>e.SayHello("你好")

var e = Expression.Parameter(typeof(MyClass), "e");
var method = typeof(MyClass).GetMethod(nameof(MyClass.SayHello), new[] { typeof(string) });
var call = Expression.Call(e, method, Expression.Constant("你好"));//调用SayHello方法并给方法传入"你好"参数
var lambda = Expression.Lambda<Func<MyClass, string>>(call, e);

调用Linq扩展方法e=>e.List.Contains("s")

首先我们要先获取到IEnumerable的Contains泛型扩展方法:

typeof(Enumerable).GetMethods().FirstOrDefault(info => info.GetParameters().Length == 2 && info.Name == "Contains").MakeGenericMethod(typeof(string))

其次扩展方法的调用需要通过一个null实例进行调用

Expression.Call(null, containsMethod, list, Expression.Constant("s"));

对于这样的链式调用,那λ表达式树的数据结构是什么样的呢?这种我们考虑从前往后构建就好了,翻译成树状结构如下:

所以完整代码如下:

var e = Expression.Parameter(typeof(MyClass), "e");
var list = Expression.Property(e, nameof(MyClass.List));//e.List
var containsMethod = typeof(Enumerable).GetMethods().FirstOrDefault(info => info.GetParameters().Length == 2 && info.Name == "Contains").MakeGenericMethod(typeof(string));
var contains = Expression.Call(null, containsMethod, list, Expression.Constant("s"));//e.List.Contains("s")
var lambda = Expression.Lambda<Func<MyClass, bool>>(contains, e);

 

以上对对象的基本操作完了,我们再来一点复杂的,调用Linq扩展方法

e=>e.List.Any(item=>item.Contains("s"))

这看上去有点懵,表达式嵌套表达式,我们结合之前的方法调用和扩展方法调用,打一套组合拳就好了,表达式它也是一种参数类型嘛,我们先从里往外,再从前往后,λ表达式树的数据结构如下,先上图再写代码就不再懵:

所以,代码如下:

var e = Expression.Parameter(typeof(MyClass), "e");
var s = Expression.Parameter(typeof(string), "s");
var anyContains = Expression.Call(s, typeof(string).GetMethod("Contains", new[] { typeof(string) }), Expression.Constant("a"));// s.Contains("a")
var containsLambda = Expression.Lambda(anyContains, s);//s=>s.Contains("a")
var any = typeof(Enumerable).GetMethods().FirstOrDefault(info => info.GetParameters().Length == 2 && info.Name == "Any").MakeGenericMethod(typeof(string));
var list = Expression.Property(e, nameof(MyClass.List));//e.List
var whereLambda = Expression.Call(null, any, list, containsLambda); // e.List.Any(s=>s.Contains("a")
var lambda = Expression.Lambda<Func<MyClass, bool>>(whereLambda, e);

对象的玩法暂时就只想到了这么多,以后想到了别的案例再随时补充。

三目表达式x=>x>60?"及格":"不及格"

这个表达式需要用到Expression.Condition,即可生成三目表达式的λ表达式树。其数据结构如下图:

代码实现如下:

var x = Expression.Parameter(typeof(int), "x");
var gt60 = Expression.GreaterThan(x, Expression.Constant(60));//x>60
var condition = Expression.Condition(gt60, Expression.Constant("及格"), Expression.Constant("不及格"));//x>60?"及格":"不及格"
var lambda = Expression.Lambda<Func<int, string>>(condition, x);//x=>x>60?"及格":"不及格"

很简单是吧,那就又来个组合拳吧。

e => e.MyBool ? "真的" : "假的";

三目运算符+成员访问,还好,不算有难度。

var e = Expression.Parameter(typeof(MyClass), "e");
var expression = Expression.Property(e, "MyBool");
var condition = Expression.Condition(expression, Expression.Constant("真的"), Expression.Constant("假的"));
var lambda = Expression.Lambda<Func<MyClass, string>>(condition, e);

null值表达式e=>e.MyProperty??"s"

Expression.Coalesce便是对标的C#6专属的null值表达式

var e = Expression.Parameter(typeof(MyClass), "e");
var expression = Expression.Property(e, "MyProperty");
var coalesce = Expression.Coalesce(expression, Expression.Constant("s"));
var lambda = Expression.Lambda<Func<MyClass, string>>(coalesce, e);

类型转换Convert.ToInt32(x)

Expression.Convert即等价于Convert静态类,如通过Expression.Convert替代Convert.ToInt32:

var convert = Expression.Convert(Expression.Constant(10.234), typeof(int));
var lambda = Expression.Lambda<Func<int>>(convert);

声明一个数组对象

Expression.NewArrayBounds即可生成一个创建数组对象的表达式:

var array = Expression.NewArrayBounds(typeof(string), Expression.Constant(5));
var lambda = Expression.Lambda<Func<string[]>>(array);

现在有了这么多的基础储备,那么接下来就来点复杂的实战吧!

实现条件表达式m=>m.MyProperty.Contains("ldqk")||m.List.Any(s=>s.Length>1&&s.Contains("a"))

我们还是先画出流程图,写代码不乱

写代码也没有必要按照刚才说的从内到外从前往后的顺序,也可以按局部到全部的方式来。我们一步一步便得到下面的代码:

var s = Expression.Parameter(typeof(string), "s");
var length = Expression.Property(s, "Length"); // s.Length
var gt10 = Expression.GreaterThan(length, Expression.Constant(1));// s.Length>1
var anyContains = Expression.Call(s, typeof(string).GetMethod("Contains", new[] { typeof(string) }), Expression.Constant("a"));// s.Contains("a")
var anyWhere = Expression.AndAlso(gt10, anyContains); // s.Length>1&&s.Contains("a")
var anyLambda = Expression.Lambda(anyWhere, s);// s=>s.Length>1&&s.Contains("a")
var any = typeof(Enumerable).GetMethods().FirstOrDefault(info => info.GetParameters().Length == 2 && info.Name == "Any").MakeGenericMethod(typeof(string));
var m = Expression.Parameter(typeof(MyClass), "m");
var prop = Expression.Property(m, "List");// List.Any(s=>s.Length>1&&s.Contains("a")
var whereLambda = Expression.Call(null, any, prop, anyLambda); // m.List.Any(s=>s.Length>1&&s.Contains("a")
var property = Expression.Property(m, nameof(MyClass.MyProperty));// m.MyProperty
var contains = Expression.Call(property, typeof(string).GetMethod("Contains", new[] { typeof(string) }), Expression.Constant("ldqk"));// m.MyProperty.Contains("ldqk")
var lambda = Expression.Lambda<Func<MyClass, bool>>(Expression.OrElse(contains, whereLambda), m); // m=>m.MyProperty.Contains("ldqk")||m.List.Any(s=>s.Length>1&&s.Contains("a"))

链式调用s=>s.List.Select(e => e.Length).OrderBy(x=>x).FirstOrDefault() > 1

这就比上面的这个例子简单多了,直接从左往右一步一步实现就好了

var s = Expression.Parameter(typeof(MyClass), "s");
var prop = Expression.Property(s, "List"); // s.List
var selectMethod = typeof(Enumerable).GetMethods().FirstOrDefault(info => info.GetParameters().Length == 2 && info.Name == "Select").MakeGenericMethod(typeof(string), typeof(int));
var e = Expression.Parameter(typeof(string), "e");
var selectlength = Expression.Property(e, "Length"); // e.Length
var selectLambda = Expression.Lambda(selectlength, e); // e => e.Length
var select = Expression.Call(null, selectMethod, prop, selectLambda); //s.List.Select(e => e.Length)
var orderby = typeof(Enumerable).GetMethods().FirstOrDefault(info => info.GetParameters().Length == 2 && info.Name == "OrderBy").MakeGenericMethod(typeof(int), typeof(int));
var parameter = Expression.Parameter(typeof(int), "x");
var orderbyLambda = Expression.Lambda(parameter, parameter); //x=>x
var orderbyCall = Expression.Call(null, @orderby, @select, orderbyLambda); //s.List.Select(e => e.Length).OrderBy(x=>x)
var first = typeof(Enumerable).GetMethods().FirstOrDefault(info => info.GetParameters().Length == 1 && info.Name == "FirstOrDefault").MakeGenericMethod(typeof(int));
var firstExp = Expression.Call(null, first, orderbyCall); //s.List.Select(e => e.Length).OrderBy(x=>x).FirstOrDefault()
var greaterThan = Expression.GreaterThan(firstExp, Expression.Constant(1)); //s.List.Select(e => e.Length).OrderBy(x=>x).FirstOrDefault() > 1
var lambda = Expression.Lambda<Func<MyClass, bool>>(greaterThan, s); //s.List.Select(e => e.Length).OrderBy(x=>x).FirstOrDefault() >= 1

λ表达式树的高级用法

由于知乎的篇幅限制,这一节内容请参考这个链接:

C#的λ表达式树(LambdaExpression)保姆级超详细简单入门教程_懒得勤快的博客_互联网分享精神​masuit.com/1795#%CE%BB%E8%A1%A8%E8%BE%BE%E5%BC%8F%E6%A0%91%E7%9A%84%E9%AB%98%E7%BA%A7%E7%94%A8%E6%B3%95

总结

其实λ表达式树并不难,看上去很高深的东西,只有理解了其中的原理,还是很快可以上手的!毕竟在实际项目中还是很广泛应用的,掌握λ表达式树打一套组合拳,就能够实现各种各样的应用,甚至动态编译!

λ表达式树的实际应用案例

给EF Core增加AddOrUpdate方法:

通过Expression表达式树,为EF Core找回AddOrUpdate方法​masuit.com/1741

给linq增加And和Or扩展:

https://github.com/ldqk/Masuit.Tools/blob/master/Masuit.Tools/Linq/LinqExtension.cs​github.com/ldqk/Masuit.Tools/blob/master/Masuit.Tools/Linq/LinqExtension.cs

通过λ表达式树自己实现一个AutoMapper:

https://github.com/ldqk/Masuit.Tools/blob/master/Masuit.Tools/Mapping/ExpressionMapper.cs​github.com/ldqk/Masuit.Tools/blob/master/Masuit.Tools/Mapping/ExpressionMapper.cs

通过λ表达式树实现对象的深克隆:

https://github.com/ldqk/Masuit.Tools/blob/master/Masuit.Tools/Mapping/ExpressionCpoier.cs​github.com/ldqk/Masuit.Tools/blob/master/Masuit.Tools/Mapping/ExpressionCpoier.cs

将字符串转换成λ表达式的项目:

https://github.com/zzzprojects/Eval-Expression.NET​github.com/zzzprojects/Eval-Expression.NET

将字符串转换成动态Linq:

https://github.com/zzzprojects/System.Linq.Dynamic​github.com/zzzprojects/System.Linq.Dynamic

动态组合条件表达式:

dbelmont/ExpressionBuilder​github.com/dbelmont/ExpressionBuilder

还有很多很多这样的项目和应用,这里就不再一一列举了,LinqToAnything!λ表达式树是linq的基石,linq是万能的!

最后,给大家留几个思考题吧

1. Expression.Lambda(Expression.Parameter(typeof(int), "x"), Expression.Parameter(typeof(int), "x"))这种写法有没有问题?为什么?

2. 使用λ表达式树如何自己实现一个字符串算式的计算,如输入:"5x3+4",输出15?

 

转自原文链接:

C#的λ表达式树(LambdaExpression)保姆级超详细简单入门教程_懒得勤快的博客_互联网分享精神​masuit.com/1795  

 

转 https://zhuanlan.zhihu.com/p/247937380?utm_medium=social&utm_oi=1339535881314226178

标签:C#,入门教程,LambdaExpression,typeof,var,lambda,Expression,表达式,Lambda
From: https://www.cnblogs.com/wl-blog/p/17010237.html

相关文章

  • springboot整合Jackson
    springboot整合JacksonJackson简介Jackson是一套适合java的数据处理工具,用于JSON格式数据的解析与生成,支持多种类型,是SpringMVC内置解析器。除了Jackson,常用的JSON解......
  • C#:获取时间戳
     ///<summary>///获取时间戳///</summary>///<paramname="isMillisecond">是否需要毫秒</param>///<returns>当前时间戳</returns>publicstaticstringGet......
  • centos7修改静态ip,主机名,主机映射
    centos7配置一.修改主机名​​root用户操作​​vim/etc/hostnamehadoop201#添加要设置的主机名​​重启虚拟机生效​​二.添加ip和hostname对应关系即主机映射vim/e......
  • CentOS 7 关闭图形界面
    centos71.关闭图形界面systemctlset-defaultmulti-user.target2.开启图形界面systemctlset-defaultgraphical.target......
  • Oracle like多条件过滤
    模糊匹配多个条件--全模糊匹配whereREGEXP_LIKE(字段名,'(匹配串1|匹配串2|...)')--右模糊匹配whereREGEXP_LIKE(字段名,'^(匹配串1|匹配串2|...)')--左模糊匹配......
  • 如何在C#中接受或拒绝 Excel 中的修订
    修订功能可以跟踪文档所有的修改,了解修改的过程,这对于团队协同文档编辑、审阅是非常有用的一个功能。将工作簿发送给他人审阅时,我们可以开启修订功能,共享工作簿被修改后,用......
  • DVWA之Command Execution篇
    CommandExecution靶机基本情况Metasploitable2中的DVWALevel:Low构造语句:;nc-e/bin/bash192.168.176.1285555其中192.168.176.128为KaliLinuxIP地址可以......
  • 什么是Docker?为什么使用docker?
    Docker是基于Go语言进行开发实现,一个开源的应用容器引擎。采用Linux内核的cgroup,namespace,以及AUFS类的UnionFS等技术,对进程进行封装隔离,也可以实现虚拟化。隔......
  • pandas 读取和保存csv文件
    目录​​读取以,号分隔的txt​​​​读取以空格或多空格或table键分隔的txt​​读取以,号分隔的txt​​Asiafootball.txt​​中国,50,50,9日本,28,9,4韩国,17,15,3伊朗,25,4......
  • redisson的Lock,SpinLock与FencedLock
    Lock redisson的基本lock实现,使用发布订阅机制实现通信可以查看源码中pubSub的相关使用SpingLock使用"ExponentialBackoffstrategy"指数退避策略实现的分布式......