在学习MVVM的过程中,其中自定义了一个超类NotificationObject,如下
public abstract class NotificationObject : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected virtual void RaisePropertyChanged(string propertyName) { PropertyChangedEventHandler handler = this.PropertyChanged; if (handler != null) { handler(this, new PropertyChangedEventArgs(propertyName)); } } protected void RaisePropertyChanged(params string[] propertyNames) { if (propertyNames == null) throw new ArgumentNullException("propertyNames"); foreach (var name in propertyNames) { this.RaisePropertyChanged(name); } } protected void RaisePropertyChanged<T>(Expression<Func<T>> propertyExpression) { var propertyName = ExtractPropertyName(propertyExpression); this.RaisePropertyChanged(propertyName); } public static string ExtractPropertyName<T>(Expression<Func<T>> propertyExpression) { if (propertyExpression == null) { throw new ArgumentNullException("propertyExpression"); } var memberExpression = propertyExpression.Body as MemberExpression; if (memberExpression == null) { throw new ArgumentException("PropertySupport_NotMemberAccessExpression_Exception", "propertyExpression"); } var property = memberExpression.Member as PropertyInfo; if (property == null) { throw new ArgumentException("PropertySupport_ExpressionNotProperty_Exception", "propertyExpression"); } var getMethod = property.GetGetMethod(true); if (getMethod.IsStatic) { throw new ArgumentException("PropertySupport_StaticExpression_Exception", "propertyExpression"); } return memberExpression.Member.Name; } }
其中有一个函数 protected void RaisePropertyChanged<T>(Expression<Func<T>> propertyExpression),这里有个Expression<Func<T>我不是很懂,后来查查是lambda表达式,好吧,说实话,我看到这个Lambda有点蛋疼菊紧...之前碰到过,没有深究,这次再试着去学习理解一下,在网上看了很多资料,其中有一位小伙伴讲的不错的,摘抄下来,哈哈。(我建议直接先去看原文,然后再返回来看我的而文章,因为我下面的例子是基于WPF讲解的,并且像一些Button的命令绑定都贱贱的用上了,担心看的人再看蒙圈了,点此阅读原文)
lambda简介
lambda运算符简化了匿名委托的使用,使代码更加简洁、优雅,据说它是微软自C#1.0后新增的最重要的功能之一。
lambda运算符:所有的lambda表达式都是用新的lambda运算符“ => ”,可以称呼这个运算符为“转到”或者“成为”。运算符将表达式分为两部分,左边指定输入参数,右边是lambda的主体。
lambda表达式:
1. 一个参数: param => expression
2. 多个参数: (paramlist) => expression
上面这些东西,先记着,看完下面的再反过来看,理解更深刻。
lambda有啥好?
根据一个demo,慢慢进行分析,例子如下
namespace LambdaDemo.ViewModel { public class Person { public string Name { get; set; } public int Age { get; set; } } public class VMLambdaStudyDemo1 { public static List<Person> PersonsList() { List<Person> persons = new List<Person>(); for (int i = 0; i < 7; i++) { Person p = new Person() {Name = i + "儿子",Age = 8-i}; persons.Add(p); } return persons; } /// <summary> /// Btn_LambdaDemo1 Click事件 /// </summary> public void Btn_LambdaDemo1_Click() { List<Person> persons = PersonsList(); persons = persons.Where(p => p.Age > 5).ToList(); //所有Age>5的Person的集合 } } }
上面的Demo工程为按下按钮Btn_LambdaDemo1后,只保留persons集合中的Age大于5的元素。这样直接看,对于我这个新手不明显,我还是喜欢打断点追踪,如下图
我在33行和35行、37行各打一个断点,运行程序
点击按钮Btn_LambdaDemo1,程序停留在第一个断点处
可以看到此时persons集合中元素为空,紧接着 F5 运行到下一个断点
此时我们发现,集合persons中已经添加了7个元素,接着F5运行到下一个断点
这个时候就会发现,集合persons中只剩下3个元素,这3个元素均满足我们的筛选条件。
到这里,可以看出咱们的这个lambda表达式 p => p.Age > 5 起作用了,但是这还不能看出lambda的好处,大家也知道,好是相对的嘛,那咱就不用lambda实现这个筛选功能,换另一种方式
如上图,我将35行屏蔽,用37行-46行代码实现这个筛选功能,你可以打断点追踪一下,最后也能实现我们的要求。到这里,上面两种方式两两比较,咱们就能像张同学说的那样“lambda确实是一个甜枣”。(这个工程比较简单,如有需要,可自行下载,点此下载)
lambda确实挺好
上面的例子中,我用一种普通的方法来与lambda比较,来说明lambda的好处,但其实理论上不应该这样比较的。因为我在前面lambda简介中提到lambda简化了匿名委托的应用,使代码更加简洁,所以理论上我应该用匿名委托的方式来衬托一下lambda的好。下面就看一下 (p=>p.Age>5)这样的表达式到底是怎么回事,我将上面的代码修改为下
namespace LambdaDemo.ViewModel { public class Person { public string Name { get; set; } public int Age { get; set; } } public class VMLambdaStudyDemo1 { //委托 delegate List<Person> UNamedWeiTuo(List<Person> persons); public static List<Person> PersonsList() { List<Person> persons = new List<Person>(); for (int i = 0; i < 7; i++) { Person p = new Person() {Name = i + "儿子",Age = 8-i}; persons.Add(p); } return persons; } public void Btn_LambdaDemo1_Click() { List<Person> persons = PersonsList(); UNamedWeiTuo uwt = WeiTuoFunc; persons = uwt(persons); } //委托方法 public static List<Person> WeiTuoFunc(List<Person> persons) { List<Person> personstmp = new List<Person>(); foreach (Person p in persons) { if (p.Age > 5) { personstmp.Add(p); } } return personstmp; } } }
在上面的代码中,我定义了一个委托UNamedWeiTuo(关于委托我就不多说了哈,请自行脑补),利用委托方法实现我们对集合persons的删选的要求。将上述的委托与lambda表达式 (p=>p.Age>5)一比较,我们就很可以看出来表达式的优点。同时也可以看出:其实表达式 (p=>p.Age>6) 中的 p 就代表委托方法中的参数,而表达式符号右边的
p.Age>5 就代表委托方法的返回结果。
lambda趁热打铁
下面就直接上张同学的更加直观的例子:
1.单个参数
使用委托方法
public class VMLambdaStudyDemo1 { //委托 逛超市 delegate int GuangChaoShi(int a); public void Btn_LambdaDemo1_Click() { GuangChaoShi gwl = JieZhang; MessageBox.Show(gwl(10) + ""); //打印结果为 "20" } public static int JieZhang(int a) { return (a + 10); } }
使用lambda表达式
public class VMLambdaStudyDemo1 { //委托 逛超市 delegate int GuangChaoShi(int a); public void Btn_LambdaDemo1_Click() { GuangChaoShi gwl = p => p + 10; MessageBox.Show(gwl(10) + ""); //打印结果为 "20" } }
Oh My LadyGaGa,That‘s greatful!,下面再来个多参数的,但是委托方法内部运算比较简单的。
2.多参数,主体运算简单
使用委托方法
public class VMLambdaStudyDemo1 { //委托 逛超市 delegate int GuangChaoShi(int a,int b); public void Btn_LambdaDemo1_Click() { GuangChaoShi gwl = JieZhang; MessageBox.Show(gwl(10,100) + ""); //打印结果为 "80" } //结账 public static int JieZhang(int a, int b ) { return (b - (a + 10)); } }
使用lambda表达式
public class VMLambdaStudyDemo1 { //委托 逛超市 delegate int GuangChaoShi(int a,int b); public void Btn_LambdaDemo1_Click() { GuangChaoShi gwl = (p,z) => z - (p + 10); MessageBox.Show(gwl(10,100) + ""); //打印结果为 "80" } }
不错不错,下面还是来个多参数的,但是委托方法内部运算较为复杂的
3.多参数,主题运算复杂
使用委托方法
public class VMLambdaStudyDemo1 { /// <summary> ///委托 逛超市 /// </summary> /// <param name="a">花费</param> /// <param name="b">付钱</param> /// <returns>找零</returns> delegate int GuangChaoShi(int a,int b); public void Btn_LambdaDemo1_Click() { GuangChaoShi gwl = JieZhang; MessageBox.Show(gwl(20,100) + ""); //打印结果为 "70" } //结账 public static int JieZhang(int a, int b ) { int zuidixiaofei = 10; if (b < zuidixiaofei) { return 100; } else { return (b - a - zuidixiaofei); } } }
使用lambda表达式
public class VMLambdaStudyDemo1 { /// <summary> ///委托 逛超市 /// </summary> /// <param name="a">花费</param> /// <param name="b">付钱</param> /// <returns>找零</returns> delegate int GuangChaoShi(int a,int b); public void Btn_LambdaDemo1_Click() { GuangChaoShi gwl = (p, z) => { int zuidixiaofei = 10; if (p < zuidixiaofei) { return 100; } else { return (z - p - zuidixiaofei); } }; MessageBox.Show(gwl(20,100) + ""); //打印结果为 "70" } }
不错不错,写到这里,我对lambda表达式突然开窍了。
上面这些例子,好好理解下,下面我继续摘抄张同学的,介绍一个系统指定的Func<T>委托。
Func<T>委托
T 是泛型的参数类型,这是一个泛型类型的委托,用起来比较方便
先上例子
public class VMLambdaStudyDemo1 { public void Btn_LambdaDemo1_Click() { Func<int, string> gwl = p => p + 10 + "--返回类型为string类型"; MessageBox.Show(gwl(10)); //打印结果为 "20--返回类型为string类型" } }
说明,这里可以看到,p为int类型参数,然而lambda主体返回的是string类型的,你看我的MessageBox.Show()里面都不用加 “” 了。
我勒个去,好简单的说。
再上一个例子
public void Btn_LambdaDemo1_Click() { Func<int, int, bool> gwl = (p, j) => { if ((p + j) == 10) { return true; } return false; }; MessageBox.Show(gwl(5,5) + ""); //打印结果为 "True" }
说明:从这个例子,我们能看到,p为int类型,j为int类型,返回值为bool类型。
看完上面的两个例子,相信大家应该明白Func<T>的用法:多个参数,前面的为委托方法的参数,最后一个参数为委托方法的返回类型。不得不说,这个张同学的思路讲的真清晰啊!
lambda表达式总结
① “lambda表达式”是一个匿名函数,它可以包含表达式和语句,并且可用于创建委托。
② lambda表达式格式为:(参数列表) => 表达式或语句块
③ 说白了,lambda表达式和匿名方法其实是一件事,它们的作用都是:产生方法。即内联方法。
④ lambda不仅可用于创建委托,还可以用于创建表达式目录树。下一小节就讲lambda表达式树!
lambda表达式树
注意上面标题是“lambda表达式树”,而不是“lambda表达式”!
● Expressions Tree概念
① 表达式树又称为表达式目录树,以数据形式表示语言级代码。所有的数据都存储在树结构中,每个节点表示一个表达式。
② Lambda表达式树允许我们像处理数据(比如读取,修改)一样来处理 lambda表达式。(注意这里可以看出lambda表达式树和lambda表达式的关系!)
● 表达式的种类(这里可以先了解一下,随着实验的深入返过来再看理解的会更深)
① 参数表达式:ParameterExpression. 就是一个方法中的参数,例如 search(string key)中的key可以看成是一个参数表达式;再例如lambda表达式中 (n => n * 3)中的n也可以看成是一个参数表达式。
② 二次元表达式:BinaryExpression. 例如 a + b 或者 (n * 3) < 5 等等。
③ 方法调用表达式:MethodCallExpression,例如自定义LINQ提供程序中实现Orderby的操作。
④ 常数表达式:ConstantExpression. 例如数值5
⑤ 带有条件运算的表达式:ConditionExpression
还有很多,可以去看MSDN,不解释。
下面举个简单的例子,还是采用断点的方式追踪
public void Btn_LambdaDemo1_Click() { Func<int, bool> test = n => (n * 3) < 5; Expression<Func<int, bool>> filter = n => (n * 3) < 5; }
从程序可以看出,test和filter均用到的 lambda表达式 n => (n * 3) < 5,但是 filter 利用Expression将lambda表达式以目录树的形式表示为数据结构,下面咱们运行程序,看看这两个到底有什么不同。
程序运行到第一个断点处
此时 test 和 filter均为空,两次F5,程序运行到第3个断点处,展开test和filter,如下图
从上图中可以看到,两者的内容大不一样,其中filter里面包含了很多 lambda表达式 (n => (n*3)<5) 的信息,比如Body主体是 (n*3)<5,返回类型ReturnType为Boolen类型,有1个参数。展开filter的Body,如下图
可以看到Body里面还可以看到主体左边为 n * 3 ,主体右边为 5,节点类型为 < 即LessThan等等,信息量好大的说。
下面通过函数直观的展现表达式树的内部信息
public class VMLambdaStudyDemo1 { public void Btn_LambdaDemo1_Click() { Func<int, bool> test = n => (n * 3) < 5; Expression<Func<int, bool>> filter = n => (n * 3) < 5; BinaryExpression lt = (BinaryExpression)filter.Body; BinaryExpression mult = (BinaryExpression)lt.Left; ParameterExpression en = (ParameterExpression)mult.Left; ConstantExpression three = (ConstantExpression)mult.Right; ConstantExpression five = (ConstantExpression)lt.Right; var One = filter.Compile(); MessageBox.Show(One(5) + " || " + One(1) + " || " + lt.NodeType + " || " + mult.NodeType + " || " + en.Name + " || " + three.Value + " || " + five.Value); } }
可以自己断点追踪一下看看,我就不演示了,截图好麻烦的说。下面是程序的运行结果
上面的例子可以直观的展示 “ Lambda表达式树允许我们像处理数据(比如读取,修改)一样来处理 lambda表达式”这一句话,再来个例子,加深印象。
using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Text; using System.Threading.Tasks; namespace LambdaDemo.ViewModel { public class VMLambdaStudyDemo1 { public void Btn_LambdaDemo1_Click() { ParameterExpression a = Expression.Parameter(typeof(int), "i"); //创建一个表达式树中的参数,作为一个节点,这里是最下层的节点 ParameterExpression b = Expression.Parameter(typeof(int), "j"); BinaryExpression be = Expression.Multiply(a, b); //这里i*j,生成表达式树中的一个节点,比上面节点高一级 ParameterExpression c = Expression.Parameter(typeof(int), "w"); ParameterExpression d = Expression.Parameter(typeof(int), "x"); BinaryExpression be1 = Expression.Multiply(c, d); BinaryExpression su = Expression.Add(be, be1); //运算两个中级节点,产生终结点 Expression<Func<int, int, int, int, int>> lambda = Expression.Lambda<Func<int, int, int, int, int>>(su, a, b, c, d); Func<int, int, int, int, int> f = lambda.Compile(); //将表达式树描述的lambda表达式,编译为可执行代码,并生成该lambda表达式的委托; System.Windows.MessageBox.Show("lambda:" + lambda + "Result:" + f(1, 1, 1, 1)); } } }
运行结果为
上段代码的lambda表达式树的结构图,直接用张同学的了
结合这张图再看代码,仔细理解下,应该能理解的比较透彻了。
结语
我勒个去,敲了这么长,比敲代码累多了,敲到这里,我对lambda算是比较理解了,但是反思一下,会用吗?答曰:不会!
不过没事儿,边学边用,下一篇准备研究一下 NotificationObject里的 protected void RaisePropertyChanged<T>(Expression<Func<T>> propertyExpression)