C#中的委托和事件——学习笔记

本文详细介绍了C#中的委托和事件机制,包括委托的定义、使用方法、事件的声明与注册,以及如何通过事件简化代码结构。通过实例演示,展示了如何利用委托和事件提高代码的灵活性和可扩展性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原文地址:http://www.tracefact.net/CSharp-Programming/Delegates-and-Events-in-CSharp.aspx

按我的思路整理一下,作者大神的一些内容理解比较困难。

委托

一、为什么使用委托

public enum Language{
    English, Chinese
}

public void GreetPeople(string name, Language lang){
    //做某些额外的事情,比如初始化之类,此处略
    swith(lang){
        case Language.English:
           EnglishGreeting(name);
           break;
       case Language.Chinese:
           ChineseGreeting(name);
           break;
    }
}

以上这段代码使用了枚举作为判断依据,在方法中运用switch判断,而如果需要增加更多的语言种类时,就会变得很麻烦,于是可以使用委托。

在GreetPeople方法中,枚举Language被用作判断,而如果我们定义一个变量,该变量指向一个方法,在调用GreetPeople方法时,直接调用参数中指向的方法,就可以免去判断,枚举这些步骤。


这种用来指向方法的参数类型,就是委托。

我准备给GreetPeople添加一个MakeGreeting的参数。

首先,声明这个委托所需要指向的两个方法:

public void EnglishGreeting(string name)
public void ChineseGreeting(string name)


于是我们就可以定义一个委托GreeetingDelegate:

public delegate void GreetingDelegate(string name);

这个委托定义了参数MakeGreeting的参数类型。


有了委托后就可以再次修改GreetPeople方法

public void GreetPeople(string name, GreetingDelegate MakeGreeting){
    MakeGreeting(name);
}

将原本的枚举Language换成了委托后,就可以将需要的方法作为参数,直接指向GreetPeople方法中所需要的方法。


完整代码范例:

using System;
using System.Collections.Generic;
using System.Text;

namespace Delegate {
     //定义委托,它定义了可以代表的方法的类型
     public delegate void GreetingDelegate(string name);
        class Program {

           private static void EnglishGreeting(string name) {
               Console.WriteLine("Morning, " + name);
           }

           private static void ChineseGreeting(string name) {
               Console.WriteLine("早上好, " + name);
           }

           //注意此方法,它接受一个GreetingDelegate类型的方法作为参数
           private static void GreetPeople(string name, GreetingDelegate MakeGreeting) {
               MakeGreeting(name);
            }

           static void Main(string[] args) {
               GreetPeople("Jimmy Zhang", EnglishGreeting);
               GreetPeople("张子阳", ChineseGreeting);
               Console.ReadKey();
           }
        }
    }

输出如下:
Morning, Jimmy Zhang
早上好, 张子阳

我们现在对委托做一个总结:

委托是一个类,它定义了方法的类型,使得可以将方法当作另一个方法的参数来进行传递,这种将方法动态地赋给参数的做法,可以避免在程序中大量使用If-Else(Switch)语句,同时使得程序具有更好的可扩展性。


二、委托的用法,将方法绑定到委托

在调用GreetPeople时,我们可以直接传入"Jimmy"到方法中,当然也可以定义一个变量name="Jimmy"。

string name1= "Jimmy Zhang";

GreetPeople(name1, EnglishGreeting);

或者

GreetPeople("Jimmy Zhang", EnglishGreeting);


同样的,既然string可以这样做,委托作为参数类型也是可以的(虽然委托在编译过程中会被编译成类)。

static void Main(string[] args) {
    GreetingDelegate delegate1, delegate2;
    delegate1 = EnglishGreeting;
    delegate2 = ChineseGreeting;

    GreetPeople("Jimmy Zhang", delegate1);
        GreetPeople("张子阳", delegate2);
        Console.ReadKey();
}

这样做事完全没问题的,而比起string,委托还有一个特性,可以将多个方法绑定到同一个委托,当调用这个委托时,将依次调用其所绑定的方法。

static void Main(string[] args) {
    GreetingDelegate delegate1;
    delegate1 = EnglishGreeting; // 先给委托类型的变量赋值
    delegate1 += ChineseGreeting;   // 给此委托变量再绑定一个方法

     // 将先后调用 EnglishGreeting 与 ChineseGreeting 方法
    GreetPeople("Jimmy Zhang", delegate1); 
    Console.ReadKey();
}

输出为:
Morning, Jimmy Zhang
早上好, Jimmy Zhang

实际上,我们可以也可以绕过GreetPeople方法,通过委托来直接调用EnglishGreeting和ChineseGreeting:

static void Main(string[] args) {
    GreetingDelegate delegate1;
    delegate1 = EnglishGreeting; // 先给委托类型的变量赋值
    delegate1 += ChineseGreeting;   // 给此委托变量再绑定一个方法

    // 将先后调用 EnglishGreeting 与 ChineseGreeting 方法
    delegate1 ("Jimmy Zhang");  
    Console.ReadKey();
}


注意这里,第一次用的“=”,是赋值的语法;第二次,用的是“+=”,是绑定的语法。如果第一次就使用“+=”,将出现“使用了未赋值的局部变量”的编译错误。


另外还有这样的简化写法:

GreetingDelegate delegate1 = new GreetingDelegate(EnglishGreeting);
delegate1 += ChineseGreeting;   // 给此委托变量再绑定一个方法


既然给委托可以绑定一个方法,那么也应该有办法取消对方法的绑定,很容易想到,这个语法是“-=”:

static void Main(string[] args) {
    GreetingDelegate delegate1 = new GreetingDelegate(EnglishGreeting);
    delegate1 += ChineseGreeting;   // 给此委托变量再绑定一个方法

    // 将先后调用 EnglishGreeting 与 ChineseGreeting 方法
    GreetPeople("Jimmy Zhang", delegate1); 
    Console.WriteLine();

    delegate1 -= EnglishGreeting; //取消对EnglishGreeting方法的绑定
    // 将仅调用 ChineseGreeting
    GreetPeople("张子阳", delegate1);
    Console.ReadKey();
}
输出为:
Morning, Jimmy Zhang
早上好, Jimmy Zhang
早上好, 张子阳


事件

一、为什么要用事件

在实际操作中,GreetPeople和ChineseGreeting、EnglishGreeting应该放在不用的类中。现在将GreetingPeople()放在一个叫GreetingManager的类中,如下:

namespace Delegate {
    //定义委托,它定义了可以代表的方法的类型
    public delegate void GreetingDelegate(string name);
   
    //新建的GreetingManager类
    public class GreetingManager{
       public void GreetPeople(string name, GreetingDelegate MakeGreeting) {
           MakeGreeting(name);
       }
    }

    class Program {
       private static void EnglishGreeting(string name) {
           Console.WriteLine("Morning, " + name);
       }

       private static void ChineseGreeting(string name) {
           Console.WriteLine("早上好, " + name);
       }

       static void Main(string[] args) {
           // ... ...
        }
    }
}


在Main()添加如下代码:

static void Main(string[] args) {
    GreetingManager gm = new  GreetingManager();
    gm.GreetPeople("Jimmy Zhang", EnglishGreeting);
    gm.GreetPeople("张子阳", ChineseGreeting);
}


成功运行了,接下来用另一种方法,将方法绑定到委托。

static void Main(string[] args) {
    GreetingManager gm = new  GreetingManager();
    GreetingDelegate delegate1;
    delegate1 = EnglishGreeting;
    delegate1 += ChineseGreeting;

    gm.GreetPeople("Jimmy Zhang", delegate1);
}

输出:
Morning, Jimmy Zhang
早上好, Jimmy Zhang


上面在调用时(即Main()方法中)才声明了delegate1这个委托类型的变量,为何不在GreetingManager类中事先声明,这样调用会更加方便。

public class GreetingManager{
    //在GreetingManager类的内部声明delegate1变量
    public GreetingDelegate delegate1; 

    public void GreetPeople(string name, GreetingDelegate MakeGreeting) {
       MakeGreeting(name);
    }
}

之后就能这样实现这个方法

static void Main(string[] args) {
    GreetingManager gm = new  GreetingManager();
    gm.delegate1 = EnglishGreeting;
    gm.delegate1 += ChineseGreeting;

    gm.GreetPeople("Jimmy Zhang", gm.delegate1);
}

输出为:
Morning, Jimmy Zhang
早上好, Jimmy Zhang


之后为了在调用时看上去更简洁

public class GreetingManager{
    //在GreetingManager类的内部声明delegate1变量
    public GreetingDelegate delegate1; 

    public void GreetPeople(string name) {
        if(delegate1!=null){    //如果有方法注册委托变量
          delegate1(name);      //通过委托调用方法
       }
    }
}


static void Main(string[] args) {
    GreetingManager gm = new  GreetingManager();
    gm.delegate1 = EnglishGreeting;
    gm.delegate1 += ChineseGreeting;

    gm.GreetPeople("Jimmy Zhang");     //注意,这次不需要再传递 delegate1变量
}

输出为:
Morning, Jimmy Zhang
早上好, Jimmy Zhang


将类中的变量定义为public是非常不安全的,所以我们要对delegate1进行封装。

如果将delegate1声明为provate会怎么样?delegate1这个变量就完全没有用了,因为无法调用这个委托,当然无法在进行方法的注册,这个委托根本没有意义。

之后作者所说的没法理解,总之这里就需要事件对delegate1进行封装(相当于用属性来封装字段)。

在用事件Event封装委托类型的变量后,在类的内部,不管你声明它是public还是protected,它总是private的。在类的外部,注册“+=”和注销“-=”的访问限定符与你在声明事件时使用的访问符相同。(好绕,不懂)


继续改写GreetingManager

public class GreetingManager{
    //这一次我们在这里声明一个事件
    public event GreetingDelegate MakeGreet;

    public void GreetPeople(string name) {
        MakeGreet(name);
    }
}


事件MakeGreet和之前委托delegate1的声明差别只有一个event关键字,事件相当于进行过封装的委托。

static void Main(string[] args) {
    GreetingManager gm = new  GreetingManager();
    gm.MakeGreet = EnglishGreeting;         // 编译错误1
    gm.MakeGreet += ChineseGreeting;

    gm.GreetPeople("Jimmy Zhang");
}

会得到编译错误:事件“Delegate.GreetingManager.MakeGreet”只能出现在 += 或 -= 的左边(从类型“Delegate.GreetingManager”中使用时除外)。

在变成gm.MakeGreet += EnglishGreeting;后正常,。

尽管在GreetingManager中奖MakeGreet声明为public,但他会被编译成私有字段,所以不允许在GreetingManger类外以赋值的方式访问。


以下是MakeGreet所产生的代码

private GreetingDelegate MakeGreet; //对事件的声明 实际是 声明一个私有的委托变量
 
[MethodImpl(MethodImplOptions.Synchronized)]
public void add_MakeGreet(GreetingDelegate value){
    this.MakeGreet = (GreetingDelegate) Delegate.Combine(this.MakeGreet, value);
}

[MethodImpl(MethodImplOptions.Synchronized)]
public void remove_MakeGreet(GreetingDelegate value){
    this.MakeGreet = (GreetingDelegate) Delegate.Remove(this.MakeGreet, value);
}

MakeGreet事件确实是一个GreetingDelegate类型的委托,只不过不管是不是声明为public,它总是被声明为private。另外,它还有两个方法,分别是add_MakeGreet和remove_MakeGreet,这两个方法分别用于注册委托类型的方法和取消注册。实际上也就是: “+= ”对应 add_MakeGreet,“-=”对应remove_MakeGreet。而这两个方法的访问限制取决于声明事件时的访问限制符。(就是声明事件时为public ,这两个方法就同样是public)










评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值