C# 委托

委托定义

委托是一个或多个函数指针的抽象。C# 为了类型安全,默认不支持指针,只能以委托的形式来实现函数指针的概念。

委托提供了一种定义和执行回调的方法。通过委托可以将函数视为数据,作为参数进行传递。

委托有以下特征:

  • 委托继承 System.MulticastDelegate 类

  • 目标方法与委托声明须一致

  • 委托可以指向静态或实例方法

  • 一旦创建委托对象,就可以在运行时动态调用它指向的方法

  • 委托可以同步和异步调用方法

委托声明和使用 

委托声明与抽象方法一模一样,只需将 abstract 关键字换成 delegate

public delegate int Operation(int x,int y);

在这里我们就声明好了一个委托类型。可以理解为这是一份委托协议,里面规定了要传进来的方法必须是两个整数且返回一个整数。

而 C# 编译器执行到这一行时,它会定义一个继承自 MulticastDelegate 的类,该类还实现了一个 Invoke 方法,该方法与委托声明中描述的方法完全相同。我们可以通过 ILDASM.exe 来看下它最终的编译。选择工具>命令行>开发者命令提示,在弹出的 CMD 输入 ildasm 就弹出 IL DASM 窗体,在 IL DASM 菜单栏选择 文件>打开,找到项目的 dll 。结果如下图

接下我们定义一个目标方法。如下 :  

/// <summary>
/// 作为参数传递的方法
/// 它与声明的 Operation 参数一致且返回类型也一样
/// </summary>
static int Add(int a,int b)
{
    //逻辑处理
	return a+b;   
}

一定要注意,目标方法与委托声明的要一致。

接着我们将目标方法插入到给定的委托对象,只需将方法名称传递给委托构造函数,如下:

Operation operation = new Operation(Add);

这时,就可以使用直接函数调用来调用指向的成员,如下:

static void Main(string[] args)
{
    //将目标方法插入到委托对象中
    Operation operation = new Operation(Add);
    //或者
    Operation operation1 = Add

    //使用直接函数调用来调用指向的成员
    int result=operation(2,5);
    
    //输出 7
    Console.WriteLine(result);

    //也可以直接调用 Invoke 方法
    result=operation1.Invoke(4,5);
    
    //输出 9
    Console.WriteLine(result);

    Console.Read();
}

方法当作参数进行传递,代码如下: 

public delegate int Operation(int x, int y);

static int Add(int a, int b)
{
    return a + b;
}

static void Main(string[] args)
{
    Sum(Add);

    Console.Read();
}

static void Sum(Operation operation)
{
    if (operation != null)
    {
        //逻辑处理,得到两个数据进行求和
        int a = 2;
        int b = 5;

        var result = operation(a, b);
        Console.WriteLine("{0} + {1} = {2}", a, b, result);

    }
}

委托数组

创建委托数组与定义数组相似,这里就看代码:

public delegate void Operation(int x, int y);

static void Add(int a,int b)
{
    Console.WriteLine("{0} + {1} = {2}", a, b, a + b);
}

static void Multiple(int a, int b)
{
    Console.WriteLine("{0} * {1} = {2}", a, b, a * b);
}
static void Main(string[] args)
{
    Operation[] operation = 
    {
        new Operation(Add),
        new Operation(Multiple)
    };

    foreach(Operation op in operation)
    {
        op(4, 7);
        op(2, 6);
        op(5, 5);
    }

    Console.Read();
}

输出的结果如下:

4 + 7 = 11
2 + 6 = 8
5 + 5 = 10
4 * 7 = 28
2 * 6 = 12
5 * 5 = 25

匿名方法

匿名方法就是无名方法。不需要创建新方法就可以完成功能,所以没必要单独创建一个方法。匿名方法在编码时提供了一种更简洁、更方便的方法。

使用委托关键字和无名方法体来定义匿名方法。

class Program  
{  
    // 定义一个委托
    delegate void Operation();  

    static void Main(string[] args)  
    {  
        // 创建实例  
        Operation opt = delegate  
        {  
            Console.WriteLine("匿名方法");  
        };  
        opt();  

        Console.ReadLine();  
    }  
}  

此代码将匿名方法分配给委托。匿名方法不能有参数和返回值。

匿名方法降低了代码的复杂性,尤其是项目使用过委托的开发者,就知道匿名方法的好处。当然,使用匿名方法不会提高代码性能,编译器仍会定义一个方法。如下图框起来的是编译器生成的匿名方法

 多播委托

多播委托看到也有叫委托链。 多播委托类似于虚拟容器,其存储着调用多个函数引用的列表。它以 FIFO(先进先出)的顺序依次调用每个方法。当你向委托对象添加多个方法时,只需使用 += 运算符,而不是直接赋值。

public delegate void Operation(int x, int y);

static void Add(int a,int b)
{
    Console.WriteLine("{0} + {1} = {2}", a, b, a + b);
}

static void Multiple(int a, int b)
{
    Console.WriteLine("{0} * {1} = {2}", a, b, a * b);
}
static void Main(string[] args)
{
    Operation operation = Add;

    operation += Multiple;

    operation(4, 7);

    Console.Read();
    
    //输出
    //2 + 5 = 7
    //2 * 5 = 10
}

这里先执行 Add 方法,再执行 Multiple 方法。这是函数指针添加到多播委托的顺序。

要从多播委托中删除函数引用,使用 -= 运算符,该运算符允许调用者从委托对象调用列表中动态删除方法。

Operation operation = Add;

operation -= Multiple;

这里会执行 Add 方法,不会执行 Multiple ,因为我们使用 -= 运算符从多播委托取消了。

一个委托调用多个方法可能会导致出现问题。如果委托调用的方法之一引发异常,则整个委托将会被中止。可以通过 Delegate 类,它定义了一个 GetInvocationList 方法,该方法返回一个 Delegate 对象数组。

public delegate void Operation(int x, int y);

static void Add(int a, int b)
{
    //Console.WriteLine("{0} + {1} = {2}", a, b, a + b);
    throw new Exception("错误");
}

static void Multiple(int a, int b)
{
    Console.WriteLine("{0} * {1} = {2}", a, b, a * b);
}
static void Main(string[] args)
{
    Operation operation = Add;

    operation += Multiple;

    Delegate[] dels = operation.GetInvocationList();
    foreach (Operation del in dels)
    {
        try
        {
            del(2, 5);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }


    Console.Read();
    
    //输出
    //错误
    //2 * 5 = 10
}

综合示例

上面是委托的基本使用,可能还体会不到它有无尽的好处,这里我们看一个综合示例。

不使用委托:

class Program
{
    public static void SpeakEnglish()
    {
        Console.WriteLine("hello , bala bala bala bala bala bala.");
    }

    public static void SpeakChinse()
    {
        Console.WriteLine("你好,你会说中文吗?会滴!");
    }

    public static void Speak(string ln)
    {
        switch (ln)
        {
            case "zh-cn":
                SpeakChinse();
                break;
            case "en":
                SpeakEnglish();
                break;
        }
    }

    static void Main(string[] args)
    {
        Speak("zh-cn");
        Speak("en");
    }
}

上面这个代码也没什么毛病,如果现在要多增加一种语言,比如说法语,西班牙语等。这时怎么办,我们要增加一个说法语的方法,然后再增加一个 case,但这样扩展起来是不是很麻烦~而且还破坏开放封闭原则。我们使用委托方式改造下:

public class Language
{

    public delegate void Speak();

    public void Say(Speak speak)
    {
        speak();
    }
}

class Program
{
    public static void SpeakEnglish()
    {
        Console.WriteLine("hello , bala bala bala bala bala bala.");
    }

    public static void SpeakChinse()
    {
        Console.WriteLine("你好,你会说中文吗?会滴!");
    }

    public static void SpeakFrench()
    {
        Console.WriteLine("Bonjour, c'est moi qui parle!");
    }

    static void Main(string[] args)
    {
        Language language = new Language();
        language.Say(SpeakChinse);
        language.Say(SpeakEnglish);
        language.Say(SpeakFrench);
    }
}

没有了 swich-case ,取而代之是委托,扩展也很方便了有木有。

今天的委托就到这了。

最后,祝大家学习愉快!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值