# 委托

## 什么是委托

C#的委托类似于C或C++中<font color="red">函数的指针</font>，**委托**是一种存着对某个方法引用的引用类型变量，引用在运行时可以被改变。或者可以这么理解，我们将委托理解为**方法的抽象**，也就是方法的模板，至于这个方法是怎么样的，就由方法自己去实现。

我们知道接口的最大好处就是可以实现多态，同理，**委托**是可以实现方法的多态，当我们想调用某个具体方法的时候，我们不直接调用这个方法，而是去调用这个委托。当然，我们必须在具体方法和委托之间建立某种关联。

委托常被用于事件和回调方法。所有委托都派生自**System.Delegate**类。
## 定义一个委托

委托使用关键字`delegate`来定义，语法如下：

```csharp
delegate <return type> <delegate-name> (params)
```
例如我们定义一个具体的委托：
```csharp
public delegate void Test();
```
这就定义了一个没有返回值和参数的委托，并且语法根定义抽象方法很类似，只不过多了一个**delegate**关键字。

<font color="red">**注意**</font>

- 委托的定义一般放到类的外部，因为大多数委托将会被频繁调用。如果定义到类的内部，则只有本类可以直接使用该委托，其他的必须通过类成员才能调用。

- 如果委托定义到类外部，一般定义为public，便于外部调用。

- 定义到类内部时，不能在方法中定义委托。

接下来，我们开始讲如何使用委托。
## 方法作为方法参数

我们先不管这个标题怎么理解，先看下面的示例代码，它们的作用就是在屏幕上输出一段话：

```csharp
namespace Test
{
    class Program
    {
        public static void Hello(string name)
        {
            EnglishHello(name);
        }

        public static void EnglishHello(string name)
        {
            Console.WriteLine("Hello,{0}",name);
        }

        static void Main(string[] args)
        {
            Hello("Liker") ;
        }
    }
}
```

`Hello` 用于向某人问好，当我们传递代表某人姓名的 name 参数，比如说“Jone”进去的时候，在这个方法中，将调用 `EnglishHello`方法，再次传递 name 参数，`EnglishHello`则用于向屏幕输出 “Hello, Jone”。

现在假设这个程序需要进行全球化，哎呀，不好了，我是中国人，我不明白“Hello”是什么意思，怎么办呢？好吧，我们再加个中文版的问候方法：

```csharp
public static void ChineseHello(string name)
{
	Console.WriteLine("你好，{0}",name);
}
```

这时候，`Hello`也需要改一改了，不然如何判断到底用哪个版本的 问候方法合适呢？在进行这个之前，我们最好再定义一个枚举作为判断的依据：

```csharp
public enum Language
{
    English, Chinese
}
 
public static void Hello(string name, Language lang)
{
    switch (lang)
    {
        case Language.English:
            EnglishHello(name);
            break;
        case Language.Chinese:
            ChineseHello(name);
            break;
    }
}
```

**OK，尽管这样解决了问题，但我不说大家也很容易想到，这个解决方案的可扩展性很差，如果日后我们需要再添加韩文版、日文版，就不得不反复修改枚举和GreetPeople() 方法，以适应新的需求。**

在考虑新的解决方案之前，我们先看看 `Hello`的方法签名：

```csharp
public static void Hello(string name, Language lang);
```

我们想，假如 `Hello()` 方法可以接受一个参数变量，这个变量可以代表另一个方法，当我们给这个变量赋值 **EnglishHello** 的时候，它代表着 `EnglishHello () `这个方法；当我们给它赋值**ChineseHello** 的时候，它又代表着 `ChineseHello () `法。我们将这个参数变量命名为 **MakeHello**，那么不是可以如同给 name 赋值时一样，在调用` Hello()`方法的时候，给这个**MakeHello**参数也赋上值么(**EnglishHello** 或者**ChineseHello** 等)？然后，我们在方法体内，也可以像使用别的参数一样使用**MakeHello**。但是，由于 **MakeHello**代表着一个方法，它的使用方式应该和它被赋的方法(比如**ChineseHello** )是一样的，比如：`MakeGreeting(name);`

好了，有了思路了，我们现在就来改改`GreetPeople()`方法，那么它应该是这个样子了：

```csharp
public void Hello(string name, xxx MakeHello)
{
    MakeHello(name);
}
```

注意到 xxx，这个位置通常放置的应该是参数的类型，但到目前为止，我们仅仅是想到应该有个可以代表方法的参数，并按这个思路去改写 `Hello`方法，现在就出现了一个大问题：这个代表着方法的 MakeHello参数应该是什么类型？

**说明：**这里已不再需要枚举了，因为在给MakeHello赋值的时候动态地决定使用哪个方法，是 `ChineseHello`还是` EnglishHello`，而在这个两个方法内部，已经对使用“Hello”还是“你好”作了区分。

聪明的你应该已经想到了，现在是委托该出场的时候了，但讲述委托之前，我们再看看MakeHello参数所能代表的 `ChineseHello`和` EnglishHello`方法的签名：

```csharp
public void EnglishHello(string name)
public void ChineseHello(string name)
```

如同 name 可以接受 String 类型的“true”和“1”，但不能接受bool 类型的true 和int 类型的1 一样，MakeHello的参数类型定义应该能够确定 MakeHello可以代表的方法种类，再进一步讲，就是 MakeHello可以代表的方法的参数类型和返回类型。

于是，委托出现了：它定义了 MakeHello参数所能代表的方法的种类，也就是 MakeHello参数的类型。

本例中委托的定义：

```csharp
public delegate void HelloDelegate(string name);
```

与上面 `EnglishHello()` 方法的签名对比一下，除了加入了delegate 关键字以外，其余的是不是完全一样？现在，让我们再次改动`Hello()`方法，如下所示：

```csharp
public delegate void HelloDelegate(string name);
public static void Hello(string name, HelloDelegate MakeHello)
{
    MakeHello(name);
}
```

如你所见，委托 HelloDelegate出现的位置与 string 相同，string 是一个类型，那么 HelloDelegate应该也是一个类型，或者叫类(Class)。但是委托的声明方式和类却完全不同，这是怎么一回事？实际上，委托在编译的时候确实会编译成类。因为 Delegate 是一个类，所以在任何可以声明类的地方都可以声明委托。更多的内容将在下面讲述，现在，请看看这个范例的完整代码：

```csharp
public delegate void HelloDelegate(string name);
 
class Test
{
    public static void EnglishHello(string name)
    {
        Console.WriteLine("Hello,{0} ", name);
    }
 
    public static void ChineseHello(string name)
    {
        Console.WriteLine("你好,{0}", name);
    }
 
    public static void Hello(string name, HelloDelegate MakeHello)
    {
        MakeHello(name);
    }
 
    static void Main(string[] args)
    {
        Hello("Liker", EnglishHello);
        Hello("李志中", ChineseHello);
        Console.ReadLine();
    }
}
```

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

## 将方法绑定到委托

看到这里，是不是有那么点如梦初醒的感觉？于是，你是不是在想：在上面的例子中，我不一定要直接在 Hello() 方法中给 name 参数赋值，我可以像这样使用变量：

```csharp
static void Main(string[] args)
{
    Hello("Liker", EnglishHello);
    Hello("李志中", ChineseHello);
    Console.ReadLine();
}
```

而既然委托 HelloDelegate和类型 string 的地位一样，都是定义了一种参数类型，那么，我是不是也可以这么使用委托？

```csharp
static void Main(string[] args)
{
    HelloDelegate delegate1, delegate2;
    delegate1 = EnglishHello;
    delegate2 = ChineseHello;
    Hello("Liker", delegate1);
    Hello("李志中", delegate2);
    Console.ReadLine();
}
```

如你所料，这样是没有问题的，程序一如预料的那样输出。这里，我想说的是委托不同于 string 的一个特性：**可以将多个方法赋给同一个委托，或者叫将多个方法绑定到同一个委托，当调用这个委托的时候，将依次调用其所绑定的方法。**在这个例子中，语法如下：

```csharp
static void Main(string[] args)
{
    HelloDelegate delegate1;
    delegate1 = EnglishHello; 
    delegate1 += ChineseHello;
    Hello("Liker", delegate1);
    Console.ReadLine();
}
```

实际上，我们可以也可以绕过`Hello`方法，通过委托来直接调用`EnglishHello` 和`ChineseHello`：

```csharp
static void Main(string[] args)
{
    HelloDelegate delegate1;
    delegate1 = EnglishHello;
    delegate1 += ChineseHello; 
    delegate1("Liker");
    Console.ReadLine();
}
```

<font color="red">**注意**</font>：这在本例中是没有问题的，但回头看下上面 `Hello() `的定义，在它之中可以做一些对于` EnglshihHello` 和 `ChineseHello` 来说都需要进行的工作，为了简便我做了省略。

**注意这里，第一次用的“=”，是赋值的语法；第二次，用的是“+=”，是绑定的语法。如果第一次就使用“+=”，将出现“使用了未赋值的局部变量”的编译错误。**我们也可以使用下面的代码来这样简化这一过程：

```csharp
HelloDelegate delegate1 = new HelloDelegate(EnglishHello);
delegate1 += ChineseHello;
```

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

```csharp
static void Main(string[] args)
{
    HelloDelegate delegate1 = new HelloDelegate(EnglishHello);
    delegate1 += ChineseHello;
    Hello("Liker", delegate1);
    Console.WriteLine();
 
    delegate1 -= EnglishHello;
    Hello("李志中", delegate1);
    Console.ReadLine();
}
```

**让我们再次对委托作个总结：**

**使用委托可以将多个方法绑定到同一个委托变量，当调用此变量时(这里用“调用”这个词，是因为此变量代表一个方法)，可以依次调用所有绑定的方法。**