Fork me on GitHub

C#学习笔记(9)委托

9.1委托的概念

请把委托看成一个类型安全的C++的函数指针(但有所不同)]可以认为委托是持有一个或多个方法的对象。当然,一般情况下你不会想要“执行”一个对象,但委托与典型的对象不同。可以执行委托,这时委托会执行它所“持有”的方法。同时委托是引用类型。

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

(1)声明一个委托类型。委托声明看上去和方法声明相似,只是没有实现块。

(2)使用该委托类型声明一个委托变量。

(3)创建一个委托类型的对象,并把它赋值给委托变量。新的委托对象包含指向某个方法的引用,这个方法的签名和返回类型必须跟第一步中定义的委托类型一致。

(4)你可以选择为委托对象添加其他方法。这些方法的签名和返回类型必须与第一步中定义的委托类型相同。

(5)在代码中你可以像调用方法一样调用委托。在调用委托的时候,其包含的每一个方法都会被执行。

 

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

型 

(1)方法的列表称为调用列表。

(2)委托持有的方法可以来自任何类或结构,只要它们在下面两方面匹配:

委托的返回类型;

委托的签名(包括ref和out修饰符)。

调用列表中的方法可以是实例方法也可以是静态方法。

(3)在调用委托的时候,会执行其调用列表中的所有方法。

 

我们将从下面的示例代码开始。

(1)代码开始部分声明了一个委托类型MyDel(没错,是委托类型不是委托对象。我们很快就会介绍这一点)。

(2)Program类声明了3个方法:PrintLow、PrintHigh和Main。接下来要创建的委托对象将持有PrintLow或PrintHigh方法,但到底使用哪个要到运行时才能确定。

(3)Main声明了一个局部变量del,它将持有一个Mybel类型的委托对象的引用。这并不会创建对象,只是创建持有委托对象引用的变量,在几行之后便会创建这个委托对象,并将其赋值给这个变量。

(4)Main创建了一个Random类的对象,Random是一个随机数生成器类。接着程序调用该对象的Next方法,将99作为方法的输入参数。这会返回一个介于0到99之间的随机整数,并将这个值保存在局部变量 randomValue 中。

(5)下面一行检查返回并存储的随机值是否小于50。(注意,我们使用三元条件运算符来返回两个委托之一。)如果该值小于50,就创建一个MyDel委托对象并初始化,让它持有PrintLow方法的引用。否则,就创建一个持有PrintHigh方法的引用的MyDel委托对象。

(6)最后,Main执行委托对象del,这将执行它持有的方法(Printlow或 PrintHight )。                                                                                                                                                                                                                                            

代码:

复制代码
using System;

delegate void MyDel(int value);

class Program
{
    void PrintLow(int value)
    {
        Console.WriteLine($"{value} - Low Value");
    }
    void PrintHigh(int value)
    {
        Console.WriteLine($"{value} - High Value");
    }

    static void Main()
    {
        Program program = new Program();
        MyDel del;
        Random rand = new Random();
        int randomValue = rand.Next(99);
        del = randomValue < 50 ? new MyDel(program.PrintLow) : new MyDel(program.PrintHigh);
        del(randomValue);
    }
}
复制代码

运行结果:

 

 

9.2 声明委托类型

委托类型必须在被用来创建变量以及类型的对象之前声明。如下示例代码声明了委托类型。

形如:

delegate  void  MyDel  (int x);

(关键字)             (签名)

 

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

 

9.3创建委托变量

委托是引用类型、因此有引用和对象。在委托类型声明之后,我们可以声明变量并创建类型的对象。如下代码演示了委托类型的变量声明:

形如:

MyDel        delVar;

(委托类型) (变量)

 

有两种创建委托对象的方式,第一种是使用带new运算符的对象创建表达式,如下面的代码所示。new运算符的操作数的组成如下。

(1)委托类型名。

(2)一组圆括号,其中包含作为调用列表中第一个成员的方法的名称。该方法可以是实例方法或静态方法。

形如:

delVar = new MyDel( myInstObj.MyM1 );//创建委托并保存引用

dVar=new MyDel (SClass.OtherM2);//创建委托并保存引用

静态方法

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

形如:

delVar=myInstObj.MyM1;//创建委托并保存引用

dVar = SClass.0therM2;//创建委托并保存引用

 

9.4组合委托

委托可以使用额外的运算得来“组合”。这个运算最终会创建一个新的委托,其调用列表连接了作为操作数的两个委托的调用列表副本。

例如:

MyDel delA=myInstObj.MyM1;

MyDel delB=SClass.OtherM2;

MyDel delC = delA + delB;//组合调用列表

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

 

 

9.5为委托添加方法

C#提供了看上去可以为委托添加方法的语法,即使用+=运算符。 

       MyDel delVar = inst.MyM1;

       delVar     +=SC1.m3;

       delVar     +=X.Act;

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

你可以为委托添加多个方法。每次添加都会在调用列表中创建一个新的元素。

 

9.6为委托移除方法

可以使用-=运算符从委托移除方法。如下代码演示了--运算符的使用与为委托添加方法一样,其实是创建了一个新的委托。新的委托是旧委托的副本——只是没有了已经被移除方法的引用。

如下是移除委托时需要记住的一些事项。

(1)如果在调用列表中的方法有多个实例,-=运算符将从列表最后开始搜索,并且移除第一个与方法匹配的实例。

(2)试图删除委托中不存在的方法将无效。

(3)试图调用空委托会抛出异常。可以通过将委托和null进行比较来判断委托的调用列表是:否为空。如果调用列表为空,则委托是null。

代码:

复制代码
using System;

delegate void MyDel(int value);

class Program
{
    void PrintLow(int value)
    {
        Console.WriteLine($"{value} - Low Value");
    }
    void PrintHigh(int value)
    {
        Console.WriteLine($"{value} - High Value");
    }

    static void Main()
    {
        Program program = new Program();
        MyDel del = new MyDel(program.PrintHigh);
        int randomValue = 121;
        del += program.PrintLow;
        del -= program.PrintHigh;
        del(randomValue);
    }
}
复制代码

运行结果:

 

9.7调用委托

(1)可以通过两种方式调用委托。一种是像调用方法一样调用委托,另一种是使用委托的Invoke方法。

(2)如下面的代码块所示,可以将参数放在调用的圆括号内。用于调用委托的参数作用于调用列表中的每个方法(除非其中一个参数是输出参数,稍后将介绍)。

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

(4)调用时委托不能为空(null),否则将引发异常。可以使用if语句进行检查,也可以使用空条件运算符和Invoke方法。

 

9.8

剩下包括带返回值的委托,带引用参数的委托的使用方法与上面一致

 

9.9匿名方法以及Lambda表达式

匿名方法样例

 

复制代码
using System;

delegate void MyDel(ref int value);
class Program
{
    void add_10_1(ref int x)
    {
        x += 10;
    }
    static void Main()
    {
        Program program = new Program();
        MyDel del = delegate (ref int x) { x += 10; };
        int num = 0;
        Console.WriteLine($"number is {num}");
        program.add_10_1(ref num);
        Console.WriteLine($"number is {num}");
        del(ref num);
        Console.WriteLine($"number is {num}");
    }
}
复制代码

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

(1)声明委托变量时作为初始化表达式。

(2)组合委托时在赋值语句的右边。

(3)为委托增加事件时在赋值语句的右边。第15章会介绍事件。

匿名方法表达式的语法包含如下组成部分。

(1)delegate类型关键字。

(2)参数列表,如果语句块没有使用任何参数则可以省略。

(3)语句块,它包含了匿名方法的代码。

delegate  ( Parameters ){ ImplementationCode }

(关键字)(参数列表)     (语句块)

 

返回类型

匿名方法不会显式声明返回值。然而,实现代码本身的行为必须通过返回一个与委托的返回类型相同的值来匹配委托的返回类型。如果委托有void类型的返回值,匿名方法就不能返回值。

 

Lambda表达式

C#3.0引入了Lambda表达式;

简化了匿名方法的语法,从而避免包含这些多余的信息。我们可能会希望使用Lambda表达式来替代匿名方法。其实,如果先引入了Lambda表达式,那么就不会有匿名方法。

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

(1)删除delegate关键字;

(2)在参数列表和匿名方法主体之间放置Lambda运算符=>。Lambda运算符读作“goes to”。

如下代码演示了这种转换。第一行演示了将匿名方法赋值给变量del。第二行演示了同样的匿名方法在被转换成Lambda表达式之后,赋值给了变量let。

MyDel del = delegate(int x){{return x+1;};//匿名方法

MyDel let =(int x)=>{ return x + 1;} ;//Lambda表达式

 

这种简单的转换少了一些多余的东西,看上去更简洁了,但是只省了6个字符。然而,编译器可以推断更多信息,所以我们可以进一步简化Lambda表达式,如下面的代码所示。

(1)编译器还可以从委托的声明中知道委托参数的类型,因此Lambda表达式允许省略类型参数,如le2的赋值代码所示。

带有类型的参数列表称为显式类型。

省略类型的参数列表称为隐式类型。

(2)如果只有一个隐式类型参数,我们可以省略两端的圆括号,如le3的赋值代码所示。(3)最后,Lambda表达式允许表达式的主体是语句块或表达式。如果语句块包含了一个返回

语句,我们可以将语句块替换为return关键字后的表达式,如le4的赋值代码所示。

MyDel del=delegate(int x)={return x+1;};...//匿名方法

MyDel le1 = (int x)=>{ return x+1;};   //Lambda表达式

MyDel le2 = (x) =>{return x+1;} ;       //Lambda表达式

MyDel le3 = x => {return x + 1;} ;      //Lambda表达式

MyDel le4 = x =>x + 1;                     //Lambda表达式

 

posted @   衔清风与共  阅读(264)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示