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表达式编译函数的委托,所以我们只需要调用他,得到的结果就是一个函数方法。
注意 如果封装的带有条件查询方法不使用表达式树 会造成全表扫描 性能很慢
C# 委托、事件、表达式树理解的更多相关文章
- 用五分钟重温委托,匿名方法,Lambda,泛型委托,表达式树
这些对老一代的程序员都是老生常谈的东西,没什么新意,对新生代的程序员却充满着魅力.曾经新生代,好多都经过漫长的学习,理解,实践才能掌握委托,表达式树这些应用.今天我尝试用简单的方法叙述一下,让大家在五 ...
- 转帖:用五分钟重温委托,匿名方法,Lambda,泛型委托,表达式树
用五分钟重温委托,匿名方法,Lambda,泛型委托,表达式树 这些对老一代的程序员都是老生常谈的东西,没什么新意,对新生代的程序员却充满着魅力.曾经新生代,好多都经过漫长的学习,理解,实践才能掌握委托 ...
- Func委托与表达式树Expression
最近在写ORM框架,其中遇到一个难点,就是作为框架调用方如何将查询条件传入框架内.其中就用到了Expression. Func委托 要Expression先要了解Func委托,Func委托的样式是: ...
- C#委托,匿名方法,Lambda,泛型委托,表达式树代码示例
第一分钟:委托 有些教材,博客说到委托都会提到事件,虽然事件是委托的一个实例,但是为了理解起来更简单,今天只谈委托不谈事件.先上一段代码: 下边的代码,完成了一个委托应用的演示.一个委托分三个步骤: ...
- 委托,匿名方法,Lambda,泛型委托,表达式树
一.委托:完成一个委托应分三个步骤://step01:首先用delegate定义一个委托;public delegate int CalculatorAdd(int x, int y);//step0 ...
- 委托 lambda表达式浅显理解
方法不能跟变量一样当参数传递,怎么办,C#定义了委托,就可以把方法当变量一样传递了,为了简单,匿名方法传递,省得再声明方法了:再简单,lambda表达式传递,比匿名方法更直观. public dele ...
- C# Lambda表达式详解,及Lambda表达式树的创建
最近由于项目需要,刚刚学完了Action委托和Func<T>委托,发现学完了委托就必须学习lambda表达式,委托和Lambda表达式联合起来,才能充分的体现委托的便利.才能使代码更加简介 ...
- C#中的Lambda表达式和表达式树
在C# 2.0中,通过方法组转换和匿名方法,使委托的实现得到了极大的简化.但是,匿名方法仍然有些臃肿,而且当代码中充满了匿名方法的时候,可读性可能就会受到影响.C# 3.0中出现的Lambda表达式在 ...
- C#复习笔记(4)--C#3:革新写代码的方式(Lambda表达式和表达式树)
Lambda表达式和表达式树 先放一张委托转换的进化图 看一看到lambda简化了委托的使用. lambda可以隐式的转换成委托或者表达式树.转换成委托的话如下面的代码: Func<string ...
随机推荐
- Java数据结构(七)—— 排序算法
排序算法(Sort Algorithm) 排序算法介绍和分类 将一组数据,依指定顺序进行排列 排序的分类 内部排序 指将需要处理的所有数据都加载到内部存储器中进行排序 外部排序 数据量过大,无法全部加 ...
- Thrift接口简介
参考地址:http://roclinux.cn/?p=3316 [thrift是什么] 计算机技术里一款著名的通信框架 – thrift框架 thrift的全名叫做Apache thrift,是一款软 ...
- MacOS Big Sur11.0升级后Eclipse启动报错
本次升级MacOS Big Sur11.0.1之后,开启Eclipse时报空指针,打开页面空白,之后卸掉, 再次安装时提示加载不到libserver.dylib 或 Could not create ...
- socket阻塞与非阻塞,同步与异步,select,pool,epool
概念理解 一.与I/O相关的五个重要概念 1. 第一个概念:用户空间与内核空间 1. 现在操作系统都是采用虚拟存储器,那么对32位操作系统而言,它的寻址空间(虚拟存储空间)为4G(2的32次方) 2. ...
- 第3.10节 Python强大的字符串格式化新功能:使用format字符串格式化
一. 引言 前面两节介绍的字符串格式化方法,都有其本身对应的缺陷,老猿不建议大家使用,之所以详细介绍主要是考虑历史代码的兼容性,方便大家理解前人留下的代码.老猿推荐大家新编码时使用format方 ...
- 使用文件描述符作为Python内置函数open的file实参调用示例
一.关于文件描述符 open()函数的file参数,除了可以接受字符串路径外,还可以接受文件描述符(file descriptor),文件描述符是个整数,对应程序中已经打开的文件. 文件描述符是操作系 ...
- 第11.16节 Python正则元字符“()”(小括号)与组(group)匹配模式
一. 什么是组 关于组匹配模式,Python官网上说得比较简单,也没有这个名词,只有组这个名词,老猿查了比较多的资料和做了相关测试之后才理解. 组匹配模式,就是在匹配的正则表达式中使用小括号" ...
- 对网页接口的追踪探索(以b站通过bv号查询av号为例
对网页接口的追踪探索(以b站通过bv号查询av号为例 序言 本文只提供一种探索网页加载时后端访问接口情况的思路,所举例子没有太大实际用处. 一 自2020年3月23日起,AV号将全面升级到BV号.但是 ...
- vue 中 this.$options.data() 重置
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- 一些vue项目
https://segmentfault.com/a/1190000010330905