委托
通过委托储存传递对方法的引用,并可以通过该引用调用方法
用方法名作为委托实例或者用lambda表达式现场创建一个方法
委托是强类型的,参数和返回值的类型是固定的
Func<>有返回值,Action<>无返回值,Predicate<>返回一个bool,称为谓词,传递进去一个数据项,返回一个boll指出该项是否符合条件
自己定义委托类型主要的优势就是可读性,预定义的委托类型就是方便拿来就用
还是写一下吧,定义委托的格式:
public delegate bool Comparer (int first, int second ) ;
委托对象都是自动创建的
所有委托都是多播委托,继承自MulticastDelegate
委托对象不可变
lambda
匿名方法和lambda统称为匿名函数,匿名方法是C#2.0用的,已经被lambda完全替代了
lambda表达式没有类型
不能用跳转语句跳转到lambda外部
确定性赋值分析在lambda内部检测不到外部局部变量的初始化情况
匿名函数的理论基础是lambda演算
委托不具有结构相等性,即使两个委托类型的对象形参和返回类型完全一致,也不能将一个类型的对象转换为另外一个
Func<int , int , bool >
bool Comparer (int , int )
Action和Func系列的泛型委托是有一定的协变和逆变性的,具体先不写了我得回去看一眼泛型协变和逆变
lambda表达式会在编译的时候转换为方法
lambda表达式内部使用了一个外部变量时,捕捉了该变量
被lambda表达式捕捉的变量生存期延长了,至少和捕捉他的委托对象一样长,如果被多个委托对象捕捉,至少和存活时间最长的委托对象一样长
捕捉了变量的lambda会被编译器转化为一个类,被捕捉的局部变量作为实例字段实现,所有使用局部变量的地方改为使用那个字段(这里其实会有一个装箱吧,如果局部变量是值类型的话)
这个生成的类称为闭包,他是一个数据结构,包含一个lambda表达式和其所需的变量
lambda表达式捕捉变量并总是使用其最新的值,而不是捕捉并保留变量在委托创建时的值,变量发生变化时,捕捉它的每个委托都看到了变化
但是这个特性就会导致在循环中捕捉循环变量的时候,最后调用委托会发现每个委托用的都是同一个值,也就是循环变量最新的状态,而不是想要的“每次捕获时循环变量的当前值”
C#5.0开始在foreach中捕获循环变量时,每一次循环迭代的循环变量都是“新”变量,每次创建委托捕获的都是不同的变量,不再共享一个变量
for循环的循环变量依然是共享同一个变量
所以在C#5.0之后,要注意在for循环中捕获变量时,所有委托捕获的都是同一个变量,共享该变量的最新状态,在foreach循环中捕获变量时,所有委托对象捕获的都是其创建时的循环变量的值,委托对象之间不共享
如果想要在for循环中每次都捕获变量的当前值,要每次都临时创建一个局部变量存一下,然后捕获这个局部变量,代码如下:
string [] languages = new []{"C++" , "Python" , "C#" , "Java" };
var actions = new List<Action>();
for (var i = 0 ; i < languages.Length; i++)
{
var x = i;
actions.Add((() => Console.WriteLine(x)));
}
foreach (var action in actions)
{
action();
}
lambda不仅可以转换为委托作为方法调用,还能转换成表达式树,转换成表达式树的lambda表达式对象代表的是对lambda表达式进行描述的数据,而不是编译好的用于执行的代码,然后可以根据表达式树的数据构造一个针对数据库的SQL查询,将这个SQL查询传递给数据库
扩展IEnumerable<T>
接口的方法获取委托参数,扩展IQueryable<T>
接口的获取表达式树参数,编译器根据查询的集合类型判断实参lambda用于创建委托还是表达式树
将lambda转换成表达式树类型,创建一个Expression<TDelegate>
类型对象,然后赋值lambda
不支持将语句lambda转换成表达式树,只有表达式lambda才能转换成表达式树
事件
发布订阅模式就是观察者模式,应对的是将某个状态的变化广播给多个订阅者
要在调用委托之前检查它的值是否为空
委托不可变,所以+=和-=实际上会创建一个全新的委托然后赋值回去?
赋值操作符会清除所有订阅者,+=会容易写成=
+=和-=分别是两个方法调用
MulticastDelegate
维护着一个Delegate
对象链表,委托实例被顺序调用,通常是按照被添加的顺序调用,但是CLI规范并没有规定,所以不应该依赖委托的调用顺序,因为可能换一个.net实现这个委托调用顺序会出问题
一个订阅者抛出了异常,链中的后续订阅者收不到通知
可以从GetInvocationList()
方法获得一份订阅者列表,然后将每一个订阅者调用放到一个try/catch块中,处理好异常再继续迭代
AggregateException
可以包装一个异常集合,集合中的异常通过InnerExceptions
访问
如果订阅者有返回值,也必须使用GetInvocationList()
获取每一个单独的返回值
事件的event
关键字主要就是两个作用,一个作用是禁止在类外使用=
运算符清除掉所有订阅者,在类外只能使用+= -=
,另一个作用就是只有包容类才能触发事件。委托的问题在于封装不充分,事件关键字的作用就是加强一下委托的封装性
可以在定义事件的时候赋值一个delegate{}
空白委托,这样如果在类内部没有重新赋值为null的可能的话,则调用不需要空检查
EventHandler
事件参数从EventArgs派生,添加附加数据
通常将sender指定为包容类,因为只有包容类才能触发这个事件
编程规范:
调用委托之前验证非空
发送静态事件时sender值为null
不要为eventArgs传null,传Empty
C#不保证接收者接收事件的顺序,跟hashset一样不要依赖顺序
事件是可以自定义的:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构