委托

什么是委托


可以认为委托是持有一个或多个方法的对象。当然,正常情况下你不想“执行”一个对象,但委托与典型对象不同。可以执行委托,这时委托会执行它所“持有”的方法。 
我们从下面的示例代码开始。具体细节将在本章剩余内容介绍。

  • 代码开始部分声明了一个委托类型MyDel(没错,是委托类型不是委托对象)
  • Program类声明了3个方法:PrintLow、PrintHigh和Main。接下来要创建的委托对象将持有PrintLow或PrintHigh方法,但具体使用哪个运行时确定
  • Main声明了局部变量del,持有一个MyDel类型的委托对象的引用。这不会创建对象。只是创建持有委托对象引用的变量,在几行后便会创建委托对象,并将值赋给这个变量
  • Main创建了Random类的对象,这是个随机数生成器类。接着调用该对象Next方法,将99作为参数。这会返回介于0到99间的随机整数,并将这个值保存在局部变量randomValue中
  • 下面一行检查这个随机值是否小于50
    • 小于50,就创建一个MyDel委托对象并初始化,让它持有PrintLow方法的引用
    • 否则,就创建一个持有PrintHigh方法引用的MyDel委托对象
  • 最后,Main执行委托对象del,这将执行它持有的方法(PrintLow或PrintHigh)

如果你有C++背景,理解委托最快的方法是把它看成一个类型安全的、面向对象的C++函数指针

delegate void MyDel(int value);//声明委托类型
class Program
{void PrintLow(int value){Console.WriteLine("{0} - Low Value",value);}void PrintHigh(int value){Console.WriteLine("{0} - High Value",value);}static void Main(){Program program=new Program();MyDel del;      //声明委托变量var rand=new Random();var randomValue=rand.Next(99);del=randomValue<50?new MyDel(program.PrintLow):new MyDel(program.PrintHigh);del(randomValue);   //执行委托}
}

委托概述


委托和类一样,是用户自定义类型。但类表示的是数据和方法的集合,而委托持有一个或多个方法,以及一系列预定义操作。 
可以通过以下操作步骤来使用委托。

  1. 声明一个委托类型。委托声明看上去和方法声明相似,只是没有实现块
  2. 使用该委托类型声明一个委托变量
  3. 创建委托类型的对象,把它赋值给委托变量。新的委托对象包括指向某个方法的引用,这个方法和第一步定义的签名和返回类型一致
  4. 你可以选择为委托对象增加其他方法。这些方法必须与第一步中定义的委托类型有相同的签名和返回类型
  5. 在代码中你可以像调用方法一样调用委托。在调用委托时,其包含的每个方法都会被执行

你可以把delegate看做一个包含有序方法列表的对象,这些方法的签名和返回类型相同。

  • 方法的列表称为调用列表
  • 委托保存的方法可以来自任何类或结构,只要它们在下面两点匹配
    • 委托的返回类型
    • 委托的签名(包括ref和out修饰符)
  • 调用列表中的方法可以是实例方法也可以是静态方法
  • 在调用委托时,会执行其调用列表中的所有方法

声明委托类型


与类一样,委托类型必须在被用来创建变量以及类型的对象前声明。声明格式如下。

 关键字      委托类型名↓            ↓
delegate void MyDel(int x);↑           ↑返回类型       签名

虽然委托类型声明看上去和方法声明一样,但它不需要在类内部声明,因为它是类型声明。

创建委托对象


委托是引用类型,因此有引用和对象。委托类型声明后,我们可以声明变量并创建类型的对象。 
有两种创建委托对象的方法,一种是使用带new运算符的对象创建表达式,如下面代码所示。

delVar=new MyDel(myInstObj.MyM1);
dVar=new MyDel(SClass.OtherM2);

我们还可以使用快捷语法,它仅由方法说明符构成。这种快捷语法能够工作是因为在方法名称和其相应的委托类型间存在隐式转换。

delVar=myInstObj.MyM1;
dVar=SClass.OtherM2;

创建委托对象会为委托分配内存,还会把第一个方法放入委托调用列表。

给委托赋值


由于委托是引用类型,我们可以通过给它赋值来改变包含在委托变量中的引用。旧的委托对象会被GC回收。

MyDel delvar;
delVar=myInstObj.MyM1;
...
delVar=SClass.OtherM2;

组合委托


迄今为止,我们见过的所有委托在调用列表中都只有一个方法。委托可以使用额外的运算符来“组合”。这个运算符会创建一个新的委托,其调用列表连接了作为操作数的两个委托的调用列表副本。 
例:创建3个委托,第3个委托由前两个组合而成。

MyDel delA=myInstObj.MyM1;
MyDel delB=SClass.OtherM2;
MyDel delC=delA+delB;

尽管术语组合委托(combining delegate)让我们觉得好想操作数委托被修改了,其实它们并没有被修改。事实上,委托是恒定的。委托对象被创建后不能再被改变。

为委托添加方法


尽管通过上一节我们知道委托是恒定的,不过C#提供了看上去可以为委托添加方法的语法,即使用+=运算符。 
例:为委托的调用列表增加两个方法。

MyDel delVar=inst.MyM1;
delVar+=SCL.m3;
delvar+=X.Act;

当然,使用+=运算符时,实际发生的是创建了一个新的委托,其调用列表是左边的委托加上右边的组合。然后将这个新的委托赋值给delVar。

从委托移除方法


我们可以使用-=运算符从委托移除方法。

delVar-=SCL.m3;

与为委托增加方法一样,其实是创建了一个新的委托。新的委托是旧委托的副本–只是没有了已经被移除方法的引用。 
移除委托时需要记住以下事项:

  • 如果在调用列表中有多个实例,-=运算符将从列表最后开始搜索,并且移除第一个与方法匹配的实例
  • 试图删除委托中不存在的方法没有效果
  • 试图调用空委托会抛出异常。我们可以通过把委托和null进行比较来判断委托列表是否为空。如果调用列表为空,则委托是null

调用委托


可以像调用方法一样简单地调用委托。调用委托的参数将会用于调用列表中的每个方法(除非有输出参数,我们稍后介绍)。 
例:delVar委托接受一个整数值。使用参数调用委托会使用相同的参数值调用它调用列表中的每个成员

MyDel delVar=inst.MyM1;
delVar+=SCL.m3;
delVar+=X.Act;
...
delVar(55);

如果一个方法在调用列表中出现多次,当委托被调用时,每次在列表中遇到该方法时它都会被调用一次。

委托示例


如下代码定义并使用了没有参数和返回值的委托。有关代码的注意事项如下:

  • Test类定义了两个打印函数
  • Main方法创建了委托的实例并增加了3个方法
  • 程序随后调用委托,调用前检测了委托是否为null
delegate void PrintFunction();
class Test
{public void Print1(){Console.WriteLine("Print1 -- instance");}public static void Print2(){Console.WriteLine("Print2 -- static");}
}
class Program
{static void Main(){var t=new Test();PrintFunction pf;pf=t.Print1;pf+=Test.Print2;pf+=t.Print1;pf+=Test.Print2;if(null!=pf){pf();}else{Console.WriteLine("Delegate is empty");}}
}

调用带返回值的委托


如果委托有返回值并且调用列表中有一个以上方法,会发生下面的情况:

  • 调用列表中最后一个方法返回的值就是委托调用的返回值
  • 调用列表中其他返回值被忽略
delegate int MyDel();
class MyClass
{int IntValue=5;public int Add2(){IntValue+=2;return IntValue;}public int Add3(){IntValue+=3;return IntValue;}
}
class Program
{static void Main(){var mc=new MyClass();MyDel mDel=mc.Add2;mDel+=mc.Add3;mDel+=mc.Add2;Console.WriteLine("Value: {0}",mDel());}
}

调用带引用参数的委托


如果委托有引用参数,参数值会根据调用列表中的一个或多个方法的返回值而改变。 
在调用委托列表中的下一个方法时,参数的新值会传给下一个方法。

delegate void MyDel(ref int X);
class MyClass
{public int Add2(ref int x){x+=2;}public int Add3(ref int x){x+=3;}static void Main(){var mc=new MyClass();MyDel mDel=mc.Add2;mDel+=mc.Add3;mDel+=mc.Add2;int x=5;mDel(ref x);Console.WriteLine("Value: {0}",x);}
}

匿名方法


匿名方法(anonymous method)是在初始化委托时内联(inline)声明的方法。 
例:第一个声明了Add20方法,第二个使用匿名方法。

class Program
{public static int Add20(int x){return x+=20;}delegate int OtherDel(int InParam);static void Main(){OtherDel del=Add20;Console.WriteLine("{0}",del(5));Console.WriteLine("{0}",del(6));}
}
class Program
{delegate int OtherDel(int InParam);static void Main(){OtherDel del=delegate(int x){return x+20;};Console.WriteLine("{0}",del(5));Console.WriteLine("{0}",del(6));}
}

使用匿名方法

我们可以在如下地方使用匿名方法。

  • 声明委托变量时作为初始化表达式
  • 组合委托时在赋值语句的右边
  • 为委托增加事件(第14章)时在赋值语句的右边
匿名方法的语法

匿名方法表达式语法包含如下:

关键字     参数列表        语句块↓           ↓             ↓
delegate(Parameters){ImplementationCode}

Lambda 表达式


在匿名方法的语法中,delegate关键字有点多余,因为编译器已经知道我们在将方法赋值给委托。我们可以很容易地通过如下步骤把匿名方法转换为Lambda表达式:

  • 删除delegate关键字
  • 在参数列表和匿名方法主体之间放Lambda运算符=>(读作goes to)。
MyDel del=delegate(int x)    {return x+1;};//匿名方法
MyDel le1=        (int x) => {return x+1;};//Lambda表达式

术语Lambda表达式来源于数学家Alonzo Church等人在1920到1930年期间发明的Lambda积分。Lambda积分是用于表示函数的一套系统,它使用希腊字母Lambda(λ)来表示无名函数。近来,函数式编程语言(如Lisp及其方言)使用这个术语来表示可以直接用于描述函数定义的表达式,表达式不再需要有名字了。

除了这种简单的转换,通过编译器的自动推断,我们可以更进一步简化Lambda表达式。

  • 编译器还可以从委托的声明中知道委托参数的类型,因此Lambda表达式允许我们省略类型参数,如le2

    • 带有类型的参数列表称为显示类型
    • 省略类型的参数列表称为隐式类型
  • 如果只有一个隐式类型参数,我们可以省略周围的圆括号,如le3
  • 最后,Lambda表达式允许表达式的主体是语句块或表达式。如果语句块包含了一个返回语句,我们可以将语句块替换为return关键字后的表达式,如le4
MyDel del=delegate(int x)    {return x+1;};
MyDel le1=        (int x) => {return x+1;};
MyDel le2=            (x) => {return x+1;};
MyDel le3=             x  => {return x+1;};
MyDel le4=             x  =>         x+1  ;

例:Lambda表达式完整示例

delegate double MyDel(int par);
class Program
{static void Main(){MyDel del=delegate(int x)    {return x+1;};MyDel le1=        (int x) => {return x+1;};MyDel le2=            (x) => {return x+1;};MyDel le3=             x  => {return x+1;};MyDel le4=             x  =>         x+1  ;Console.WriteLine("{0}",del(12));Console.WriteLine("{0}",le1(12));Console.WriteLine("{0}",le2(12));Console.WriteLine("{0}",le3(12));Console.WriteLine("{0}",le4(12));}
}

有关Lambda表达式的参数列表的要点如下:

  • Lambda表达式参数列表中的参数必须在参数数量、类型和位置上与委托相匹配
  • 表达式的参数列表中的参数不一定需要包含类型(隐式类型),除非委托由ref或out参数–此时必须注明类型(显式类型)
  • 如果只有一个参数,并且是隐式类型的,周围的圆括号可以省略
  • 如果没有参数,必须使用一组空圆括号

from: http://www.cnblogs.com/moonache/p/6269113.html

转载于:https://www.cnblogs.com/GarfieldEr007/p/10126584.html

C#图解教程 第十三章 委托相关推荐

  1. C#图解教程(第三章)

    C#图解教程第三章 3.1 C#程序是一组类型声明 3.2 类型是一种模板 3.3 实例化类型 3.4 数据成员和函数成员 3.5预定义类型 3.6用户定义类型 3.7 栈和堆 3.7.1 栈 3.7 ...

  2. python 教程 第十三章、 特殊的方法

    第十三章. 特殊的方法 1)    特殊的方法 __init__(self,...) 这个方法在新建对象恰好要被返回使用之前被调用. __del__(self) 恰好在对象要被删除之前调用. __st ...

  3. Flask 教程 第十三章:国际化和本地化

    本文转载自:https://www.jianshu.com/p/e2923f4042d6 这是Flask Mega-Tutorial系列的第十三部分,我将告诉你如何扩展Microblog应用以支持多种 ...

  4. 史上最简单的spark教程第十三章-SparkSQL编程Java案例实践(终章)

    Spark-SQL的Java实践案例(五) 本章核心:JDBC 连接外部数据库,sparkSQL优化,故障监测 史上最简单的spark教程 所有代码示例地址:https://github.com/My ...

  5. Flask 教程 第十三章:国际化和本地化 1

    本文转载自:https://www.jianshu.com/p/e2923f4042d6 这是Flask Mega-Tutorial系列的第十三部分,我将告诉你如何扩展Microblog应用以支持多种 ...

  6. C#图解教程 第二十一章 命名空间和程序集

    命名空间和程序集 引用其他程序集 在第1章中,我们在高层次上观察了编译过程.编译器接受源代码文件并生称名称为程序集的输出文件.这一章中,我们将详细阐述程序集以及它们是如何生成和部署的.你还会看到命名空 ...

  7. C#图解教程 第六章 深入理解类

    深入理解类 类成员 前两章阐述了9种类成员中的两种:字段和方法.本章将会介绍除事件(第14章)和运算符外的其他类成员,并讨论其特征. 成员修饰符的顺序 字段和方法的声明可以包括许多如public.pr ...

  8. C#图解教程 第七章 类和继承

    类和继承 类继承 访问继承的成员 所有类都派生自object类 屏蔽基类的成员 基类访问 使用基类的引用 虚方法和覆写方法 覆写标记为override的方法 覆盖其他成员类型 构造函数的执行 构造函数 ...

  9. 数学分析教程 第十三章学习感受

    这一章讲的是场的数学,也就是梯度,散度,旋度的基本知识.梯度在图像处理中边沿检测还用到过,而且是离散的差分形式,其他就没咋用过了,到时里面的很多公式在数理方程中似曾相识(不过工作中我也用不到了).不过 ...

最新文章

  1. 开涛spring3(2.1) - IoC基础
  2. HDU4549 M斐波那契数列 —— 斐波那契、费马小定理、矩阵快速幂
  3. 攻击者使用“非恶意软件”也能识别,将在RSA 2017上发布的新技术
  4. maven 项目报错Context initialization failed
  5. 之前安装的python环境可以删除吗-在Mac上删除自己安装的Python方法
  6. 消息中间件学习总结(9)——RocketMQ与kafka差异比较分析
  7. Leetcode:8.string-to-integer-atoi(字符串转整数)
  8. [洛谷P2370]yyy2015c01的U盘
  9. 一个火车上遇到的女孩所引发的联想
  10. H2_Database 概述、下载与安装、及使用入门
  11. redis java 下载_linux系统下安装redis以及java调用redis
  12. kali linux安装QQ linux版教程
  13. SpreadJS 15.0 隆重登场 SpreadJS
  14. Vue安装及环境配置、开发工具
  15. Third season fifth episode,Phoebe‘s brother Frank came to see her
  16. JS基础—选项卡套选项卡(函数传参)
  17. Linux系统(三) 系统基础
  18. LeCo-83.删除排序链表中的重复元素
  19. ijkplayer播放器架构从原型到升级
  20. python爬虫--爬取-猫眼电影-代码

热门文章

  1. bootstrap 辅助类
  2. THINKPHP中使用swoole
  3. sysbench相关
  4. [从零开始]HelloWorld——第一个应用程序
  5. Exchange 2013 、Lync 2013、SharePoint 2013 三
  6. [转] 81条经典话语~~~当裤子失去皮带,才懂得什麽叫做依赖
  7. SQL 死锁分析(转贴)
  8. Mr.J--ES6特性学习笔记
  9. china-pub近7日计算机图书排行榜
  10. Java中Cookie常用操作类(Spring中操作Cookie)