【C#】 C# 委托

Posted by 西维蜀黍 on 2018-02-23, Last Modified on 2021-09-21

1 含义

委托(delegate)本质来说就是一个指向方法(method)的指针(pointer)。

通过这个委托(指针)可以调用(invoke)一个方法;也可以将委托作为一个方法(称为B方法)参数,在调用B方法时将一个委托实例传入(最终实现了将一个方法传递进入另一个方法中)。

2 声明

delegate是一个用于声明委托的关键字。

委托的声明与定义方法类型,只是需要多增加delegate关键字,且没有方法体。

public delegate void Del(string message);

// Create a method for a delegate.
public static void DelegateMethod(string message)
{
    System.Console.WriteLine(message);
}


// Instantiate the delegate.
Del handler = DelegateMethod;

// Call the delegate.
handler("Hello World");

委托的签名(参数和返回值类型)必须和待委托方法的签名(参数和返回值类型)完全一致。

3 例子

例子1

public class MrZhang
{
    //其实买车票的悲情人物是小张
    public static void BuyTicket()
    {
        Console.WriteLine("NND,每次都让我去买票,鸡人呀!");
    }
}

//小明类
class MrMing
{
    //声明一个委托,其实就是个“命令”
    public delegate void BugTicketEventHandler();

    public static void Main(string[] args)
    {
        BugTicketEventHandler myDelegate1 = new BugTicketEventHandler(MrZhang.BuyTicket);
        
        // 在C# 2.0中,加入了一种更简洁的表达
        BugTicketEventHandler myDelegate2 = MrZhang.BuyTicket;
       

        myDelegate1();

        myDelegate2();
         
        // 执行结果是BugTicketEventHandler()被调用了两次
        
        Console.ReadKey();
    }
} 

这里的执行结果是BugTicketEventHandler()被调用了两次(输出了两次NND,每次都让我去买票,鸡人呀!),

注意,这里委托的签名(public delegate void BugTicketEventHandler();)和待委托方法的签名(public static void BuyTicket())完全一致的。

例子2

C# 1.0和此之前,委托仅能被这样表达:

// Declare a delegate.
delegate void Del(string str);

// Declare a method with the same signature as the delegate.
static void Notify(string name)
{
    Console.WriteLine("Notification received for: {0}", name);
}



// Create an instance of the delegate.
Del del1 = new Del(Notify);

C# 2.0中,加入了一种更简洁的表达方式。即,直接用函数的名称赋值给委托实例。

// C# 2.0 provides a simpler way to declare an instance of Del.
Del del2 = Notify;

同时,也可以使用匿名函数(anonymous method)来声明并初始化一个委托实例。

// Instantiate Del by using an anonymous method.
Del del3 = delegate(string name)
    { Console.WriteLine("Notification received for: {0}", name); };

C# 3.0中,增加了可以用Lambda表达式(Lambda expression)来实例化委托实例的方式。

// Instantiate Del by using a lambda expression.
Del del4 = name =>  { Console.WriteLine("Notification received for: {0}", name); };

委托的应用

如前面提到的,我们也可以使用委托,以实现将一个A方法(作为一个B方法的调用参数)传递到B方法内部。回调(callback)则是这样的一种典型的使用方式。

public delegate void Del(string message);

public void MethodWithCallback(int param1, int param2, Del callback)
{
    callback("The number is: " + (param1 + param2).ToString());
}

//output: The number is: 3
MethodWithCallback(1, 2, handler);

说明

我们不能将C#中的委托(delegate)完全理解成C语言中的函数指针(即,它允许你传递一个方法到另一个方法)。

delegate有许多函数指针不具备的特点:
特点1:

函数指针只能指向静态函数,而委托既能指向(引用)静态函数(在C#中以static修饰的函数),也可以引用非静态成员函数。

// Declare a delegate
delegate void Del();

class SampleClass
{
    public void InstanceMethod()
    {
        System.Console.WriteLine("A message from the instance method.");
    }

    static public void StaticMethod()
    {
        System.Console.WriteLine("A message from the static method.");
    }
}

class TestSampleClass
{
    static void Main()
    {
        SampleClass sc = new SampleClass();

        // Map the delegate to the instance method:
        Del d = sc.InstanceMethod;
        d();

        // Map to the static method:
        d = SampleClass.StaticMethod;
        d();
    }
}
/* Output:
    A message from the instance method.
    A message from the static method.
*/

在引用非静态成员函数时,委托不仅需要保存对此函数入口地址的引用,还需要保存该函数对应的类实例对象的引用。

特点2:

与函数指针相比,委托是面向对象、类型安全、可靠的受控(managed)对象

也就是说,runtime 能够保证委托指向一个有效的方法,因此你无需担心委托指向了无效的地址或者越界地址。

4 多播委托(Multicast Delegates)

委托是一个指向方法的指针,而与普通指针唯一不同的是,委托可以同时指向一个或多个不同的方法,这就是多播委托(Multicast Delegates)

public class MethodClass
{
    public void Method1(string message) { }
    public void Method2(string message) { }
}

MethodClass obj = new MethodClass();
Del d1 = obj.Method1;
Del d2 = obj.Method2;
Del d3 = DelegateMethod;

// step1
//Both types of assignment are valid.
Del allMethodsDelegate = d1 + d2;
allMethodsDelegate += d3;

// step2
//remove Method1
allMethodsDelegate -= d1;

// copy AllMethodsDelegate while removing d2
Del oneMethodDelegate = allMethodsDelegate - d2;

我们可以仅仅简单地通过+-将不同的方法在一个委托的调用列表(invocation list)中添加或删除。

在这个例子中,经过step1后,委托实例allMethodsDelegate指向了三个方法(Method1(),Method2()DelegateMethod()),这意味着如果此时调用委托实例(通过allMethodsDelegate()`的方式),这三个方法会依次被执行。

在经过step2后,此时如果调用委托实例,则只有两个方法(Method2()DelegateMethod())会被依次执行。

多播委托的应用

**多播委托被广泛应用在事件处理(event handling)中。**即,事件源对象(event source object)发送事件通知(event notification)给已经注册了该事件的事件接受者对象。

当然,在此之前。事件接受者对象首先需要创建一个响应该事件对应的处理方法,再声明一个指向该处理方法的委托,并将该委托实例提供给事件源对象。

这样之后,当该事件发生时,事件源对象就可以通过调用委托,从而调用事件接受者对应的事件响应方法(以完成事件通知)。

Reference