[C#]委托事件lambda

委托

  • 通过委托储存传递对方法的引用,并可以通过该引用调用方法
  • 用方法名作为委托实例或者用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)
//假定有Comparer类型的变量c,需要赋值给Func<int, int, bool>类型的变量f
//直接赋值是不行的,因为没办法转换类型
//但是可以写成f = c.Invoke
//等于让f委托对象引用了c的方法
  • 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++)
// {
//     actions.Add(() => {Console.WriteLine(i);});
// }
//Output:
//4
//4
//4
//4
for (var i = 0; i < languages.Length; i++)
{
    var x = i;
    actions.Add((() => Console.WriteLine(x)));
}
//0
//1
//2
//3
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
    image
    • 事件参数从EventArgs派生,添加附加数据
    • 通常将sender指定为包容类,因为只有包容类才能触发这个事件
  • 编程规范:
    • 调用委托之前验证非空
    • 发送静态事件时sender值为null
    • 不要为eventArgs传null,传Empty
    • C#不保证接收者接收事件的顺序,跟hashset一样不要依赖顺序
  • 事件是可以自定义的:
    image
posted @   被窝儿  阅读(52)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示