zoukankan      html  css  js  c++  java
  • 《深入理解C#》整理3-委托

    一、向笨拙的委托语法说拜拜

    在C# 1中,我们一般是先写好一连串事件处理程序,然后到处写new EventHandler,这显得很多余、很凌乱,因为事件本身已经指定了它要使用哪个委托类型。有时,我们写的方法是如此简单,以至于它们的实现比签名都要短。而这一切只是由于委托需要以方法的形式来执行代码。这样一来,在创建委托实例的代码和调用委托实例时应该执行的代码之间,就“多绕了一道弯子”。

    二、方法组转换

    在C# 1中,如果要创建一个委托实例,就必须同时指定委托类型和要执行的操作。C# 2支持从方法组到一个兼容委托类型的隐式转换(方法组就是一个方法名,它可以选择添加一个目标)。例如:Thread t =new Thread(new ThreadStart(MyMethod))可以简化为Thread t=new Thread(MyThread)

    三、协变性和逆变性

    在C#4之前的版本,委托这一部分是包含协变性和逆变性的。在静态类型的情况下,如果能调用一个方法,并且在能调用一个特定委托类型的实例并使用其返回值的任何地方都能使用该方法的返回值,就可以使用该方法来创建该委托类型的一个实例。(个人小结:协变修饰返回值,指某个返回类型可以由其父类替换;逆变修饰传入的参数,值该参数类型可以由其父类替换)

    1、委托参数的逆变性

    根据约定,事件处理方法的签名应包含两个参数。第1个参数是object类型,代表事件的来源;第2个参数则负责携带与事件有关的任何额外信息,它的类型派生自EventArgs。在提供对逆变性的支持之后,你可以使用一个具有EventHandler签名的方法,作为符合约定的所有委托类型的操作。

    2、委托返回类型的协变性

    示例:

    image-20201022221045607

    ①声明委托类型的返回类型是Stream;②GenerateRandomData方法的返回类型是MemoryStream;③利用返回类型的协变性来允许GenerateSampleData用于StreamFactory;④调用委托实例时,编译器已经不知道返回的是一个MemoryStream——如果将stream变量的类型变成MemoryStream,会报告一个编译错误

    3、不兼容的风险

    C# 2的这种新的灵活性会使本来有效的C# 1代码在C# 2编译时产生不同的结果。假设一个派生类重载了某个基类中声明的方法,我们打算使用方法组转换创建一个委托的实例。由于C# 2中的协变性和逆变性,一个以前只和基类方法匹配的转换,现在也和派生类方法相匹配。在这种情况下,编译器将选择派生类方法。

    四、匿名方法中的捕获变量

    匿名方法允许你指定一个内联委托实例的操作,作为创建委托实例表达式的一部分。匿名方法还以闭包(closure)的形式提供了一些更加强大的行为。

    相关定义:

    • 闭包:一个函数除了能通过提供给它的参数交互之外,还能同环境进行更大程度的互动
    • 外部变量:作用域内包括匿名方法的局部变量或参数(不包括ref和out参数),在类的实例成员内部的匿名方法中,this引用也被认为是一个外部变量
    • 捕获的外部变量:通常简称为捕获变量,它是在匿名方法内部使用的外部变量

    示例:

    image-20201024095717856

    1、捕获变量的行为与作用

    被匿名方法捕捉到的确实是变量,而不是创建委托实例时该变量的值。简单地说,捕获变量能简化避免专门创建一些类来存储一个委托需要处理的信息。

    2、捕获变量的延长生存期

    对于一个捕获变量,只要还有任何委托实例在引用它,它就会一直存在。假设存在一个捕获变量X,那么编译器将额外创建一个类来容纳捕获变量X,委托所在的类及委托自身都拥有对该类的一个实例的引用,这个实例和其他实例一样都在堆上。除非委托准备好被垃圾回收,否则那个实例是不会被回收的。综上:局部变量并非始终是“局部”的,即使在方法返回之后,它依然存在!

    3、局部变量实例化

    每声明一次局部变量,它就被实例化一次。而当一个变量被捕获时,捕捉的是变量的“实例”

    示例:

    image-20201024110118342

    4、共享和非共享的变量混合使用

    示例:

    image-20201024114309533

    首先考虑一下outside变量。声明该变量的作用域只进入了一次①,所以很简单——它只有一个。inside变量则不同——每次循环迭代,都会实例化一个新的inside变量。这意味着当我们创建委托实例时,outside变量将由两个委托实例共享,但每个委托实例都有它们自己的inside变量。循环结束后,我们创建的第1个委托实例被调用了3次。由于它每次都要对捕获到的变量进行递增,而且每个变量的初始值都是0,所以会看到先输出的是(0,0),然后是(1,1),再是(2,2)。执行第2个委托实例时,两个变量在作用域上的区别就变得非常明显了。在第2个委托实例中,有1个不同的inside变量,所以它的初始值仍为0,但共享的outside变量已经递增了3次。第2个委托实例被调用两次,所以,输出的先是(3,0),然后是(4,1)。编译器是如何实现的?可参照下图的简单说明:

    image-20201024114425666

    5、捕获变量的使用规则

    使用捕获变量时,请参照以下规则:

    • 如果用或不用捕获变量时的代码同样简单,那就不要用。
    • 捕获由for或foreach语句声明的变量之前,思考你的委托是否需要在循环迭代结束之后延续,以及是否想让它看到那个变量的后续值。如果不是,就在循环内另建一个变量,用来复制你想要的值。(在C# 5中,你不必担心foreach语句,但仍需小心for语句。)
    • 如果创建多个委托实例(不管是在循环内,还是显式地创建),而且捕获了变量,思考一下是否希望它们捕捉同一个变量。
    • 如果捕捉的变量不会发生改变(不管是在匿名方法中,还是在包围着匿名方法的外层方法主体中),就不需要有这么多担心。
    • 如果你创建的委托实例永远不从方法中“逃脱”,换言之,它们永远不会存储到别的地方,不会返回,也不会用于启动线程——那么事情就会简单得多。
    • 从垃圾回收的角度,思考任何捕获变量被延长的生存期。这方面的问题一般都不大,但假如捕获的对象会产生昂贵的内存开销,问题就会凸现出来。

    6、小结

    • 捕获的是变量,而不是创建委托实例时它的值
    • 捕获的变量的生存期被延长了,至少和捕捉它的委托一样长
    • 多个委托可以捕获同一个变量;但在循环内部,同一个变量声明实际上会引用不同的变量“实例”
    • 在for循环的声明中创建的变量仅在循环持续期间有效——不会在每次循环迭代时都实例化。这一情况对于C# 5之前的foreach语句也适用
    • 必要时创建额外的类型来保存捕获变量
    作者:Jscroop
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
  • 相关阅读:
    如何在tomcat安装部署php项目
    十大建站开源程序
    虚拟主机、VPS、云主机以及独立服务器的关系
    heritrix启动问题修正
    网页布局:float与position的区别
    C#中利用委托实现多线程跨线程操作
    Java Service Wrapper配置详解
    Windows7部署WordPress傻瓜式教程(IIS7.5+MySQL+PHP+WordPress)
    关于favicon.ico的使用
    使用JAVA对字符串进行DES加密解密(修正问题)
  • 原文地址:https://www.cnblogs.com/Jscroop/p/13875123.html
Copyright © 2011-2022 走看看