C# 委托、事件、表达式树理解
1.什么是委托?
委托是一种动态调用方法的类型,属于引用型。
委托是对方法的抽象和封装。委托对象实质上代表了方法的引用(即内存地址)
所有的异步都是委托 委托就是函数当入参 委托被各种语法糖遮蔽了 =>就是委托 匿名委托
委托的声明原型是
delegate <函数返回类型> <委托名> (<函数参数>)
例子:public delegate void MyDelegate(int number);//定义了一个委托MyDelegate,它可以注册返回void类型且有一个int作为参数的函数
需求:遇到到不同国家的人,以不同方式跟他打招呼。
public delegate void sayhellodelegate(string name); class Program { static void Main(string[] args) { List<People> pp = new List<People>(); pp.Add(new People { Name = "马大云", Country = "中国" ,sayfunction= Chinesenihao }); pp.Add(new People { Name = "Bill Gat", Country = "USA" ,sayfunction= EnglishHello }); pp.ForEach(p => Say(p)); } public static void Say(People p) { p.sayfunction(p.Name); } public static void Chinesenihao(string name) { Console.WriteLine($"{name},老表,吃饭没?"); } public static void EnglishHello(string name) { Console.WriteLine($"hi,{name},the weather is nice today."); } } public class People { public string Name { get; set; } public string Country { get; set; } public sayhellodelegate sayfunction { get; set; } }
上面的代码中,sayhellodelegate当做一种类型在用。delegate 是表示对具有特定参数列表和返回类型的方法的引用的类型。
新需求:遇到到不同国家的人,以不同方式跟他打招呼,如果有多个国家的国籍,择使用多种方式打招呼。
委托加减运算(多播委托)
static void Main(string[] args) { List<People> pp = new List<People>(); var t = new People { Name = "马大云", Country = "中国", sayfunction = Chinesenihao }; t.Country = "中国,USA"; t.sayfunction += EnglishHello; pp.Add(t); pp.Add(new People { Name = "Bill Gat", Country = "USA", sayfunction = EnglishHello }); pp.ForEach(p => Say(p)); }
他代码不动,在给Sayfunction 赋值时坐了追加就满足了需求。
这是delegate的另外一个特性:
可以将多个方法赋给同一个委托,或者叫将多个方法绑定到同一个委托,当调用这个委托的时候,将依次调用其所绑定的方法。
当然可以追加,也可以取消。
t.sayfunction -= EnglishHello;
调用也可以使用BeginInvoke
sayfunction.BeginInvoke("Hello~I'm being invoked!", null, null);
上面使用的的Invoke 省略了
区别在于Invoke是同步,BeginInvoke是异步
特点:
- 委托类似于 C++ 函数指针,但委托完全面向对象,不像 C++ 指针会记住函数,委托会同时封装对象实例和方法。
- 委托允许将方法作为参数进行传递。
- 委托可用于定义回调方法。
- 委托可以链接在一起;例如,可以对一个事件调用多个方法。
- 委托签名不需要与方法精确匹配。
- C# 2.0 版引入了匿名方法的概念,可以将代码块作为参数(而不是单独定义的方法)进行传递。 C# 3.0 引入了 Lambda 表达式,利用它们可以更简练地编写内联代码块。 匿名方法和 Lambda 表达式(在某些上下文中)都可编译为委托类型。 这些功能现在统称为匿名函数。
- Delegate至少0个参数,至多32个参数,可以无返回值,也可以指定返回值类型。这个是祖宗。
Func可以接受0个至16个传入参数,必须具有返回值。
Action可以接受0个至16个传入参数,无返回值。
Predicate只能接受一个传入参数,返回值为bool类型。
应用场景
1.比如跨线程更新winform UI,线程调用处理等等
2.传递方法;把方法包裹起来, 传递逻辑。异步多线程执行
3.委托是一个类,它定义了方法的类型,使得可以将方法当作另一个方法的参数来进行传递,这种将方法动态地赋给参数的做法,可以避免在程序中大量使用If … Else(Switch)语句,同时使得程序具有更好的可扩展性。
Lambda表达式实现匿名委托
public partial class WebForm3 : System.Web.UI.Page { public delegate int CalculatorAdd(int x, int y); protected void Page_Load(object sender, EventArgs e) { //方法一: CalculatorAdd cAdd1 = (int x, int y) => { return x + y; }; int result1 = cAdd1(5, 6); //方法二: CalculatorAdd cAdd2 = (x, y) => { return x + y; }; int result2 = cAdd2(5, 6); //方法三: CalculatorAdd cAdd3 = (x, y) => x + y; int result3 = cAdd2(5, 6); } }
4大内置委托
Action:可以传入参数,没有返回值的委托
static void Main(string[] args) { Action<int, int> action = new Action<int, int>(addVoid); // Action<int, int> 这就是方法的原型 action(1, 2); action.Invoke(2, 3); //基本的使用方法和delege都是差不多的,包括异步的写法也是相同的, //但是他升级了,厉害之处是加入了lamda, Action<int, int>就是声明委托原型 简化了写法,通过lamda (n,m)=>{} 匿名函数的写法 Action<int, int> actionOne = new Action<int, int>((n, m) => { Console.WriteLine("lamda方式1 计算结果{0}", (n + m)); }); actionOne.Invoke(4, 5); //lamda 搞法很优雅 Action<int, int> actionTwo = (n, m) => { Console.WriteLine("lamda方式2 计算结果{0}", (n + m)); }; actionTwo.Invoke(3, 4); Console.ReadKey(); } static void addVoid(int a, int b) { Console.WriteLine("计算结果{0}", (a + b)); }
Func: 跟Action 的区别是可以有返回值 (下面代码没有使用表达式树Expression 会出现全表扫描)
/// <summary> /// 扩展方法 /// </summary> public static class DelegateExtend { /// <summary> /// 模仿Linq的Where操作 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="scoure">数据源</param> /// <param name="func">委托(自定义bool条件)</param> /// <returns></returns> public static IEnumerable<T> ExtWhere<T>(this IEnumerable<T> scoure, Func<T, bool> func) { //遍历数据源的数据 foreach (var item in scoure) { //请求委托完成条件的筛选返回bool bool bResult = func(item); //把通过筛选提交的数据源,返回出去 if (bResult) { yield return item; } } } } //查询出所有数据 IEnumerable<Student> student = sql.QueryList<Student>(); //定义一个匿名方法,并赋值给委托 Func<Student, bool> func = delegate(Student s) { //自定义代码逻辑,返回bool类型 return s.Id.Equals("1"); }; //传入委托 IEnumerable<Student> list = student.ExtWhere(func); //第二种方法,使用linq语法(自定义逻辑) IEnumerable<Student> list1 = student.ExtWhere(p => p.Id.Equals("1")); 上面就是一个简单但很常见的委托使用场景 从侧面理解一下这段代码, ExtWhere 是我要做的一件事情,但这件事情里面我需要一个bool类型的返回结果,那么我委托func去帮我获取到这个bool类型的结果 我刚开始的时候,对委托的理解觉得很困难,总感觉晕晕的,但是自己没事多练习练习之后,就会很好理解了 上面的demo很好的解释了使用委托的好处 解耦:抽出自定义逻辑,保留相同的逻辑,使代码分离 最大限度的简化代码:解耦的同时,又减少了代码量(自定义逻辑,可以避免相同逻辑的代码重复)
Comparison:返回整数,比较两个对象 (应用于集合排序)
List<Student> list=new List<Student>(); //Student类中含有Age属性 list.AddRange(....); //添加数据 //以下对Student集合按照其Age属性从小到大排序 list.Sort( (x, y) => { if (x.Age < y.Age) { return -1; } else if (x.Age > y.Age) { return 1; } else return 0; } ); 或者以下更加简单的写法********: list.sort((x,y)=>x.Age.CompareTo(y.Age));
Predicate:返回bool ,根据条件筛选(Predicate<T>又是对Func<T, bool>的简化)
Predicate<int> myPredicate = i => i > 10;
多播委托
实例化委托时必须将一个匹配函数注册到委托上来实例化一个委托对象,但是一个实例化委托不仅可以注册一个函数还可以注册多个函数,注册多个函数后,在执行委托的时候会根据注册函数的注册先后顺序依次执行每一个注册函数。
函数注册委托的原型:
<委托类型> <实例化名>+=new <委托类型>(<注册函数>)
例如:MyDelegate _myDelegate+=new MyDelegate(CheckMod);//将函数CheckMod注册到委托实例_checkDelegate上
在.net 2.0开始可以直接将匹配的函数注册到实例化委托:
<委托类型> <实例化名>+=<注册函数>
例如:MyDelegate _myDelegate+=CheckMod;//将函数CheckMod注册到委托实例_myDelegate上
注意:委托必须先实例化以后,才能使用+=注册其他方法。如果对注册了函数的委托实例从新使用=号赋值,相当于是重新实例化了委托,之前在上面注册的函数和委托实例之间也不再产生任何关系。
有+=注册函数到委托,也有-=解除注册
例如:MyDelegate _myDelegate-=CheckMod;
多播委托可以带返回值,但是只有最后一个方法的返回值会被返回。如果在委托注册了多个函数后,如果委托有返回值,那么调用委托时,返回的将是最后一个注册函数的返回值。
Action doSome = new Action(DoSome); doSome += new Action(DoSome); doSome += DoSome; doSome();//按顺序执行,最后结果是执行3次DoSome方法 doSome -= DoSome;//减少一次DoSome执行 doSome();//按顺序执行,最后结果是执行2次DoSome方法 多播委托,按顺序执行,多播委托,用Action, Func带返回值的只执行完后,只得到最后一个结果,所以没有意义。
事件(Event)
1.什么是事件
谈到委托,必提事件,事件本质是对委托的封装,对外提供add_EventName(对应+=)和remove_EventName(对应-=)访问,从而实现类的封装性。
基本上说是一个用户操作,如按键、点击、鼠标移动等等,或者是一些提示信息,如系统生成的通知。应用程序需要在事件发生时响应事件。例如,中断。
C# 中使用事件机制实现线程间的通信。
+=就是發生新事件的同時通知你;
-=就是發生新事件的同時不通知你;
事件是以委托为基础。委托是调用回调方法的一种类型安全的方式。对象凭借回调方法接收他们订阅的通知
回调机制的应用非常多,例如控件事件、异步操作完成通知等等;.net 通过委托来实现回调函数机制。相比其他平台的回调机制,委托提供了更多的功能,例如它确保回调方法是类型安全的,支持顺序调用多个方法,以及调用静态方法和实例方法。
事件在类中声明且生成,且通过使用同一个类或其他类中的委托与事件处理程序关联。包含事件的类用于发布事件。这被称为 发布器(publisher) 类。其他接受该事件的类被称为 订阅器(subscriber) 类。事件使用 发布-订阅(publisher-subscriber) 模型。
发布器(publisher) 是一个包含事件和委托定义的对象。事件和委托之间的联系也定义在这个对象中。发布器(publisher)类的对象调用这个事件,并通知其他的对象。
订阅器(subscriber) 是一个接受事件并提供事件处理程序的对象。在发布器(publisher)类中的委托调用订阅器(subscriber)类中的方法(事件处理程序)。
事件能解决什么问题
将公有的委托变量定义为私有变量,从而满足类的封装性原则;
具有委托具有的作用;
怎么使用事件
声明委托
声明事件
事件注册方法
事件机制
事件的本质就是委托,向外提供两个访问方法add_EventName(对应+=)和remove-EventName(对应-=),我们通过.NET Reflector反汇编工具来查看,到底是不是这样的。
2.声明事件(Event)
在类的内部声明事件,首先必须声明该事件的委托类型。例如:
/// <summary> /// 发生连接事件时的委托 /// </summary> /// <param name="sender"></param> /// <param name="socket">连接的socket</param> public delegate bool OnConnectHandler(object sender, System.Net.Sockets.Socket socket); /// <summary> /// 发生连接断开事件时的委托 /// </summary> /// <param name="sender"></param> /// <param name="socket">连接的socket</param> public delegate void OnDisConnectHandler(object sender, System.Net.Sockets.Socket socket); /// <summary> /// 基本的收到信号事件委托 /// </summary> /// <typeparam name="T">数据类型</typeparam> /// <param name="socket">接收到数据的具体连接</param> /// <param name="obj">数据内容</param> public delegate void OnReceiveHandler<T>(T obj); /// <summary> /// 发生异常的事件委托 /// </summary> /// <typeparam name="T">异常类型</typeparam> /// <param name="exception">异常信息</param> public delegate void OnExceptionHandler<T>(object sender, T exception);
然后,声明事件本身,使用 event 关键字:
/// <summary> /// 连接到服务器的通知 /// </summary> public event OnConnectHandler OnConnect = null; /// <summary> /// 连接断开的通知 /// </summary> public event OnDisConnectHandler OnDisConnect = null; /// <summary> /// 接收到数据的通知 /// </summary> public event OnReceiveHandler<NetData> OnReceive = null; /// <summary> /// 异常通知 /// </summary> public event OnExceptionHandler<Exception> OnException = null;
上面的代码定义了一个名为 OnReceiveHandler的委托和一个名为 OnReceive 的事件,该事件在生成的时候会调用委托。
3.事件和委托的联系与区别
从事件的声明,我们可以大致看出事件与委托的关系,事件是委托的特殊实现,事件是建立在对委托的语言支持之上的。
委托是一种类型,事件是委托类型的一个实例,加上了event的权限控制,限制权限,只允许在事件声明类里面去invoke和赋值,不允许外面,甚至子类调用。
/// <summary> /// 接收网络数据 /// </summary> private SmTcpClient mTcpRecv = null; mTcpRecv = new SmTcpClient(Config.CenterIp, Config.CenterPort, true); mTcpRecv.OnReceive += MTcpRecv_OnReceive; mTcpRecv.OnException += MTcpRecv_OnException; mTcpRecv.OnDisConnect += MTcpRecv_OnDisConnect; mTcpRecv.OnConnect += MTcpRecv_OnConnect; mTcpRecv.Start();
4.事件基本用法
1.定义委托。
public delegate void SomeKindOfDelegate(string result);
2.定义事件。
public event SomeKindOfDelegate aDelegate;
3.为事件添加响应函数。
process.Exited += new EventHandler(CmdProcess_Exited);
4.为事件规定触发(调用)方式。(【也可以没有触发方式,直接invoke】)
解说:
C#里,每一种‘事件Event'大概都对应着其‘事件处理者EventHandler'。比如Process类的OutputDataReceived事件对应着DataReceivedEventHandler,对于非特异性的‘事件',比如PasswordChanged 这种,它们统一都对应着RoutedEventHandler或者EventHandler这种较为通用的‘事件处理者'。然而,‘EventHandler'也只是充当了一个中介的角色,真正触发了‘Event'之后要做什么,还需要我们手动指定,像这样:
process.Exited += new EventHandler(CmdProcess_Exited); // 注册进程结束事件 。
EventHandler本来也是委托。比如
public delegate void DataReceivedEventHandler(object sender, DataReceivedEventArgs e);
表达式树Expression
里先讲解下表达式和表达式树,表达式相信大家都知道,比如x+5或者5,都可以算是表达式,而表达式树里面的树指的二叉树,也就是表达式的集合,C#中的Expression类就是表达式类。对于一棵表达式树,其叶子节点都是参数或者常数,非叶子节点都是运算符或者控制符。
在.Net 里面的Linq to SQL就是对表达式树的解析
C# 编译器只能从表达式 Lambda(或单行 Lambda)生成表达式树
Expression<Func<int, bool>> lambda = num => num < 5;
在表达式创建那,我们组合创建了一个Lambda表达式,那么应该怎么使用它呢?在“表达式的解析”里面,LambdaExpression类和Expression<TDelegate>类都有一个Compile的方法,学名是Lambda表达式的委托,其实就是Lambda表达式编译函数的委托,所以我们只需要调用他,得到的结果就是一个函数方法。
注意 如果封装的带有条件查询方法不使用表达式树 会造成全表扫描 性能很慢