在学习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)