# C# 委托

> 原文： [https://zetcode.com/lang/csharp/delegates/](https://zetcode.com/lang/csharp/delegates/)

C# 教程的这一部分专门针对委托。

委托是.NET Framework 使用的一种类型安全的函数指针。 委托通常用于实现回调和事件监听器。 委托无需了解其使用的方法类的任何知识。

委托是引用类型。 但是委托不是引用对象，而是引用方法。

在以下情况下使用委托：

*   事件处理器
*   回调
*   LINQ
*   设计模式的实现

委托没有什么可以用常规方法完成的。 之所以使用委托，是因为它们带来了许多优点。 它们提高了应用和代码重用的灵活性。 像接口一样，委托使我们能够解耦和泛化我们的代码。 委托还允许将方法作为参数传递。 当我们需要确定在运行时调用哪种方法时，可以使用委托。 最后，委托提供了一种无需对子类进行子类化就可以对它的行为进行专门化的方法。 类可能具有复杂的泛型行为，但仍应专门化。 类是通过继承或通过委托来专用的。

## C# 使用委托

我们将有一些简单的示例显示如何使用委托。

`Program.cs`

```cs
using System;

namespace SimpleDelegate
{
    delegate void MyDelegate();

    class Program
    {
        static void Main(string[] args)
        {
            var md = new MyDelegate(MyCallback);
            md();
        }

        static void MyCallback()
        {
            Console.WriteLine("Calling callback");
        }
    }
}

```

我们声明一个委托，创建该委托的实例并调用它。

```cs
delegate void MyDelegate();

```

这是我们的委托声明。 它不返回任何值，不接受任何参数。

```cs
var md = new MyDelegate(MyCallback);

```

我们创建委托的实例。 调用时，委托将调用静态`Callback()`方法。

```cs
md();

```

我们打电话给委托。

```cs
$ dotnet run
Calling callback

```

这是输出。

我们可以使用不同的语法来创建和使用委托。

`Program.cs`

```cs
using System;

namespace SimpleDelegate2
{
    delegate void MyDelegate();

    class Program
    {
        static void Main(string[] args)
        {
            MyDelegate del = MyCallback;
            del();
        }

        static void MyCallback()
        {
            Console.WriteLine("Calling callback");
        }
    }
}

```

创建委托的实例时，我们可以保存一些类型。

```cs
MyDelegate del = MyCallback;

```

这是创建委托的另一种方法。 我们直接指向方法名称。

## C# 委托指向不同的方法

委托可以随着时间指向不同的方法。

`Program.cs`

```cs
using System;

namespace DifferentMethods
{
    public delegate void NameDelegate(string msg);

    public class Person
    {
        public string firstName;
        public string secondName;

        public Person(string firstName, string secondName)
        {
            this.firstName = firstName;
            this.secondName = secondName;
        }

        public void ShowFirstName(string msg)
        {
            Console.WriteLine(msg + this.firstName);
        }

        public void ShowSecondName(string msg)
        {
            Console.WriteLine(msg + this.secondName);
        }
    }

    class Program
    {
        public static void Main()
        {
            var per = new Person("Fabius", "Maximus");

            var nDelegate = new NameDelegate(per.ShowFirstName);
            nDelegate("Call 1: ");

            nDelegate = new NameDelegate(per.ShowSecondName);
            nDelegate("Call 2: ");
        }
    }
}

```

在此示例中，我们只有一名委托。 该委托用于指向`Person`类的两个方法。 方法与委托一起调用。

```cs
public delegate void NameDelegate(string msg);

```

使用`delegate`关键字创建委托。 委托签名必须与委托调用的方法的签名匹配。

```cs
var nDelegate = new NameDelegate(per.ShowFirstName);
nDelegate("Call 1: ");

```

我们创建一个新委托的实例，该实例指向`ShowFirstName()`方法。 稍后我们通过委托调用该方法。

```cs
$ dotnet run
Call 1: Fabius
Call 2: Maximus

```

这两个名称都是通过委托打印的。

## C# 多播委托

多播委托是一个拥有对多个方法的引用的委托。 多播委托必须仅包含返回`void`的方法，否则将存在运行时异常。

`Program.cs`

```cs
using System;

namespace MulticastDelegate
{
    delegate void MyDelegate(int x, int y);

    public class Oper
    {
        public static void Add(int x, int y)
        {
            Console.WriteLine("{0} + {1} = {2}", x, y, x + y);
        }

        public static void Sub(int x, int y)
        {
            Console.WriteLine("{0} - {1} = {2}", x, y, x - y);
        }
    }

    class Program
    {
        static void Main()
        {
            var del = new MyDelegate(Oper.Add);

            del += new MyDelegate(Oper.Sub);
            del(6, 4);

            del -= new MyDelegate(Oper.Sub);
            del(2, 8);
        }
    }
}

```

这是一个多播委托的示例。

```cs
delegate void MyDelegate(int x, int y);

```

我们的委托接受两个参数。 我们有一个`Oper`类，它具有两个静态方法。 一个将两个值相加，另一个将两个值相减。

```cs
var del = new MyDelegate(Oper.Add);

```

我们创建委托的实例。 委托指向`Oper`类的静态`Add()`方法。

```cs
del += new MyDelegate(Oper.Sub);
del(6, 4);

```

我们将另一个方法插入到现有的委托实例中。 委托的第一次调用将调用两个方法。

```cs
del -= new MyDelegate(Oper.Sub);
del(2, 8);

```

我们从委托中删除一种方法。 委托的第二次调用仅调用一种方法。

```cs
$ dotnet run
6 + 4 = 10
6 - 4 = 2
2 + 8 = 10

```

这是程序的输出。

## C# 匿名方法

可以对委托使用匿名方法。

`Program.cs`

```cs
using System;

namespace Anonymous
{
    delegate void MyDelegate();

    class Program
    {
        static void Main(string[] args)
        {
            MyDelegate del = delegate
            {
                Console.WriteLine("Anonymous method");
            };

            del();
        }
    }
}

```

当将匿名方法与委托一起使用时，我们可以省略方法声明。 该方法没有名称，只能通过委托来调用。

```cs
MyDelegate del = delegate
{
    Console.WriteLine("Anonymous method");
};

```

在这里，我们创建一个指向匿名方法的委托。 匿名方法的主体用`{}`字符括起来，但是没有名称。

## C# 委托作为方法参数

委托可以用作方法参数。

`Program.cs`

```cs
using System;

namespace MethodParameters
{
    delegate int Arithm(int x, int y);

    class Program
    {
        static void Main()
        {
            DoOperation(10, 2, Multiply);
            DoOperation(10, 2, Divide);
        }

        static void DoOperation(int x, int y, Arithm del)
        {
            int z = del(x, y);
            Console.WriteLine(z);
        }

        static int Multiply(int x, int y)
        {
            return x * y;
        }

        static int Divide(int x, int y)
        {
            return x / y;
        }
    }
}

```

我们有一个`DoOperation()`方法，该方法将一个委托作为参数。

```cs
delegate int Arithm(int x, int y);

```

这是一个委托声明。

```cs
static void DoOperation(int x, int y, Arithm del)
{
    int z = del(x, y);
    Console.WriteLine(z);
}

```

这是`DoOperation()`方法的实现。 第三个参数是委托。 `DoOperation()`方法调用一个方法，该方法作为第三个参数传递给它。

```cs
DoOperation(10, 2, Multiply);
DoOperation(10, 2, Divide);

```

我们称为`DoOperation()`方法。 我们传递两个值和一个方法给它。 我们对这两个值的处理方式取决于我们通过的方法。 这就是使用委托所带来的灵活性。

```cs
$ dotnet run
20
5

```

This is the output.

## C# 事件

事件是由某些操作触发的消息。 单击按钮或滴答滴答即是这种动作。 触发事件的对象称为发送者，而接收事件的对象称为接收者。

按照约定，.NET Framework 中的事件委托具有两个参数：引发事件的源和事件的数据。

`Program.cs`

```cs
using System;

namespace SimpleEvent
{
    public delegate void OnFiveHandler(object sender, EventArgs e);

    class FEvent
    {
        public event OnFiveHandler FiveEvent;

        public void OnFiveEvent()
        {
            if (FiveEvent != null)
            {
                FiveEvent(this, EventArgs.Empty);
            }
        }
    }

    class Program
    {
        static void Main()
        {
            var fe = new FEvent();
            fe.FiveEvent += new OnFiveHandler(Callback);

            var random = new Random();

            for (int i = 0; i < 10; i++)
            {
                int rn = random.Next(6);

                Console.WriteLine(rn);

                if (rn == 5)
                {
                    fe.OnFiveEvent();
                }
            }
        }

        public static void Callback(object sender, EventArgs e)
        {
            Console.WriteLine("Five Event occurred");
        }
    }
}

```

我们有一个简单的示例，可以在其中创建和启动事件。 生成一个随机数。 如果数字等于 5，则会生成`FiveEvent`事件。

```cs
public event OnFiveHandler FiveEvent;

```

使用`event`关键字声明事件。

```cs
fe.FiveEvent += new OnFiveHandler(Callback);

```

在这里，我们将名为`FiveEvent`的事件插入到`Callback()`方法中。 换句话说，如果触发了`ValueFive`事件，则将执行`Callback()`方法。

```cs
public void OnFiveEvent()
{
    if(FiveEvent != null)
    {
        FiveEvent(this, EventArgs.Empty);
    }
}

```

当随机数等于 5 时，我们调用`OnFiveEvent()`方法。 在这种方法中，我们引发了`FiveEvent`事件。 此事件不包含任何参数。

```cs
$ dotnet run
1
1
5
Five Event occurred
1
1
4
1
2
4
5
Five Event occurred

```

这是一个示例输出。

## C# 复杂事件示例

接下来，我们有一个更复杂的示例。 这次，我们将通过生成的事件发送一些数据。

`Program.cs`

```cs
using System;

namespace ComplexEvent
{
    public delegate void OnFiveHandler(object sender, FiveEventArgs e);

    public class FiveEventArgs : EventArgs
    {
        public int count;
        public DateTime time;

        public FiveEventArgs(int count, DateTime time)
        {
            this.count = count;
            this.time = time;
        }
    }

    public class FEvent
    {
        public event OnFiveHandler FiveEvent;

        public void OnFiveEvent(FiveEventArgs e)
        {
            FiveEvent(this, e);
        }
    }

    public class RandomEventGenerator
    {
        public void Generate()
        {
            int count = 0;
            FiveEventArgs args;

            var fe = new FEvent();
            fe.FiveEvent += new OnFiveHandler(Callback);

            var random = new Random();

            for (int i = 0; i < 10; i++)
            {
                int rn = random.Next(6);

                Console.WriteLine(rn);

                if (rn == 5)
                {
                    count++;
                    args = new FiveEventArgs(count, DateTime.Now);
                    fe.OnFiveEvent(args);
                }
            }
        }

        public void Callback(object sender, FiveEventArgs e)
        {
            Console.WriteLine("Five event {0} occurred at {1}",
                e.count, e.time);
        }
    }

    class Program
    {
        static void Main()
        {
            var reg = new RandomEventGenerator();
            reg.Generate();
        }
    }
}

```

我们有四个类。 `FiveEventArgs`带有事件对象的一些数据。 `FEvent`类封装了事件对象。 `RandomEventGenerator`类负责生成随机数。 它是事件发送者。 最后，`ComplexEvent`是主要的应用类。

```cs
public class FiveEventArgs : EventArgs
{
    public int count;
    public DateTime time;
...

```

`FiveEventArgs`在事件对象内部传送数据。 它继承自`EventArgs`基类。 计数和时间成员是将被初始化并随事件一起携带的数据。

```cs
if (rn == 5)
{
    count++;
    args = new FiveEventArgs(count, DateTime.Now);
    fe.OnFiveEvent(args);
}

```

如果生成的随机数等于 5，我们用当前计数和`DateTime`值实例化`FiveEventArgs`类。 `count`变量对生成此事件的次数进行计数。 `DateTime`值保存事件生成的时间。

```cs
$ dotnet run
4
4
1
3
3
3
2
5
Five event 1 occurred at 10/22/2019 2:13:10 PM
3
0

```

这是程序的示例输出。

## C# 预定义的委托

.NET 框架具有多个内置的委托，这些委托减少了所需的输入并简化了开发者的编程工作。

### C# 动作委托

操作委托封装了没有参数且不返回值的方法。

`Program.cs`

```cs
using System;

namespace ActionDelegate
{
    class Program
    {
        static void Main()
        {
            Action act = ShowMessage;
            act();
        }

        static void ShowMessage()
        {
            Console.WriteLine("C# language");
        }
    }
}

```

使用预定义的委托可以进一步简化编程。 我们不需要声明委托类型。

```cs
Action act = ShowMessage;
act();

```

我们实例化一个动作委托。 委托指向`ShowMessage()`方法。 调用委托时，将执行`ShowMessage()`方法。

有多种类型的动作委托。 例如，`Action<T>`委托封装了一个采用单个参数且不返回值的方法。

`Program.cs`

```cs
using System;

namespace ActionDelegate2
{
    class Program
    {
        static void Main()
        {
            Action<string> act = ShowMessage;
            act("C# language");
        }

        static void ShowMessage(string message)
        {
            Console.WriteLine(message);
        }
    }
}

```

我们修改前面的示例，以使用带有一个参数的动作委托。

```cs
Action<string> act = ShowMessage;
act("C# language");

```

我们创建`Action<T>`委托的实例，并使用一个参数对其进行调用。

### C# 谓词委托

谓词是一种返回`true`或`false`的方法。 谓词委托是对谓词的引用。 谓词对于过滤值列表非常有用。

`Program.cs`

```cs
using System;
using System.Collections.Generic;

namespace PredicateDelegate
{
    class Program
    {
        static void Main()
        {
            List<int> vals = new List<int> { 4, 2, 3, 0, 6, 7, 1, 9 };

            Predicate<int> myPred = greaterThanThree;

            List<int> vals2 = vals.FindAll(myPred);

            foreach (int i in vals2)
            {
                Console.WriteLine(i);
            }
        }

        static bool greaterThanThree(int x)
        {
            return x > 3;
        }
    }
}

```

我们有一个整数值列表。 我们要过滤所有大于三的数字。 为此，我们使用谓词委托。

```cs
List<int> vals = new List<int> { 4, 2, 3, 0, 6, 7, 1, 9 };

```

这是整数值的一般列表。

```cs
Predicate<int> myPred = greaterThanThree;

```

我们创建一个谓词委托的实例。 委托指向谓词，这是一种返回`true`或`false`的特殊方法。

```cs
List<int> vals2 = vals.FindAll(myPred);

```

`FindAll()`方法检索与指定谓词定义的条件匹配的所有元素。

```cs
static bool greaterThanThree(int x)
{
    return x > 3;
}

```

对于大于三个的所有值，谓词返回`true`。

C# 教程的这一部分专用于委托。