C#之委托🔥

1/20/2024 CSharp

# 什么是委托

在 C# 中,委托(Delegate)是一种类型,它可以存储对方法的引用,并且可以在运行时动态地将这些引用传递给其他方法。简而言之,委托可以看作是方法的类型安全的指针。

委托允许您将方法作为参数传递给其他方法,或者在运行时确定要调用的方法。这使得委托在事件处理、回调函数等方面非常有用。委托的声明类似于方法的声明,但它具有 delegate 关键字。委托可以被实例化,并且可以将方法添加到委托的调用列表中,这样当委托被调用时,它所包含的所有方法都将被依次调用。

# 1、委托声明

委托可以声明在类外部,也可以在类内部

// 定义一个委托,它可以指向一个接受一个字符串参数并返回 void 的方法
public delegate void MyDelegate(string message);
// 一个方法,符合委托的签名
public void DisplayMessage(string message)
{
    Console.WriteLine("Message: " + message);
}
1
2
3
4
5
6
7

# 2、委托实例化

a、通过new实例化

MyDelegate myDelegate = new MyDelegate(DisplayMessage);
1

b、将方法分配给委托类型

MyDelegate myDelegate = DisplayMessage;
1

c、声明匿名方法

MyDelegate myDelegate = Delegate(string message){Console.WriteLine("Message: " + message);}
1

d、使用 lambda 表达式

MyDelegate myDelegate = message => Console.WriteLine("Message: " + message);
1

# 3、委托调用

myDelegate.invoke("委托被调用了!");
//或者
myDelegate("委托被调用了!");
1
2
3

# 委托实战

首先假设我们有一个场景,根据用户输入的不同命令执行不同的操作。使用委托可以让我们以一种更加模块化和可维护的方式组织代码,而不是将所有的逻辑都塞进一个庞大的 switch 语句中。 让我们首先使用 switch 语句来实现功能:

class Program
{
    static void Main(string[] args)
    {
        // 模拟用户输入的命令
        string userInput = "sayHello";

        // 根据用户输入的命令执行相应的操作
        switch (userInput)
        {
            case "sayHello":
                SayHello();
                break;
            case "sayGoodbye":
                SayGoodbye();
                break;
            default:
                Console.WriteLine("Invalid command.");
                break;
        }
    }

    // 不同命令对应的方法
    static void SayHello()
    {
        Console.WriteLine("Hello!");
    }

    static void SayGoodbye()
    {
        Console.WriteLine("Goodbye!");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

这个示例使用 switch 语句根据用户输入的命令执行相应的操作。虽然在这个简单的示例中 switch 语句看起来并不复杂,但随着命令数量的增加,switch 语句可能会变得臃肿且难以维护。在稍微复杂的情况下,使用委托和字典等方法会更加灵活和可扩展。

下面用委托来实现:

// 定义一个委托,它可以指向执行不同命令的方法
public delegate void CommandHandler();

class Program
{
    static void Main(string[] args)
    {
        // 通过命令名称将委托和方法关联起来
        Dictionary<string, CommandHandler> commandDictionary = new Dictionary<string, CommandHandler>
        {
            { "sayHello", SayHello },
            { "sayGoodbye", SayGoodbye }
        };

        // 模拟用户输入的命令
        string userInput = "sayHello";

        // 查找并执行用户输入命令对应的方法
        if (commandDictionary.ContainsKey(userInput))
        {
            CommandHandler handler = commandDictionary[userInput];
            handler();
        }
        else
        {
            Console.WriteLine("Invalid command.");
        }
    }

    // 不同命令对应的方法
    static void SayHello()
    {
        Console.WriteLine("Hello!");
    }

    static void SayGoodbye()
    {
        Console.WriteLine("Goodbye!");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

在这个示例中,我们使用委托 CommandHandler 来定义执行不同命令的方法。然后,我们使用一个 Dictionary 将命令名称和对应的方法关联起来。当用户输入命令时,我们查找字典并执行相应的方法。这种方法相比于 switch 语句更加灵活,因为我们可以轻松地添加新的命令和对应的操作,而不必修改大的 switch 语句。

# Action

在 C# 中,Action 是一个泛型委托类型,它可以引用不返回值的方法。Action 委托可以引用最多 16 个参数的方法,且不返回任何值。它是一个预定义的委托类型,用于表示不带返回值的方法。 Action 委托通常用于表示需要执行某些操作但不需要返回结果的方法。它可以用于许多场景,例如事件处理、异步编程中的回调函数等。

以下是 Action 委托的一些示例用法:

class Program
{
    static void Main(string[] args)
    {
        // 使用 Action 委托引用一个不带参数的方法
        Action method1 = SayHello;
        method1(); // 输出:Hello!

        // 使用 Action 委托引用一个带参数的方法
        Action<string> method2 = SayMessage;
        method2("Hi there"); // 输出:Hi there

        // 使用 Action 委托引用一个带多个参数的方法
        Action<string, int> method3 = DisplayInfo;
        method3("John", 30); // 输出:Name: John, Age: 30
    }

    static void SayHello()
    {
        Console.WriteLine("Hello!");
    }

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

    static void DisplayInfo(string name, int age)
    {
        Console.WriteLine("Name: " + name + ", Age: " + age);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

在这个示例中,我们使用了不同的 Action 委托实例来引用不同的方法,包括不带参数、带一个参数和带多个参数的方法。

# Func

在 C# 中,Func 是另一个泛型委托类型,它可以引用带有返回值的方法。与 Action 不同,Func 委托可以引用带有返回值的方法,其最后一个类型参数表示方法的返回类型。Func 委托也可以引用最多 16 个参数的方法。

以下是 Func 委托的一些示例用法:

class Program
{
    static void Main(string[] args)
    {
        // 使用 Func 委托引用一个不带参数且返回字符串的方法
        Func<string> method1 = GetGreeting;
        Console.WriteLine(method1()); // 输出:Hello!

        // 使用 Func 委托引用一个带一个参数且返回整数的方法
        Func<int, int> method2 = DoubleNumber;
        Console.WriteLine(method2(5)); // 输出:10

        // 使用 Func 委托引用一个带多个参数且返回布尔值的方法
        Func<string, string, bool> method3 = AreEqual;
        Console.WriteLine(method3("hello", "hello")); // 输出:True
    }

    static string GetGreeting()
    {
        return "Hello!";
    }

    static int DoubleNumber(int num)
    {
        return num * 2;
    }

    static bool AreEqual(string str1, string str2)
    {
        return str1 == str2;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

在这个示例中,我们使用了不同的 Func 委托实例来引用不同的方法,包括不带参数且返回字符串、带一个参数且返回整数、带多个参数且返回布尔值的方法。

# 多播委托

多播委托是一种特殊的委托,它可以持有对一个或多个方法的引用。当多播委托调用时,它将依次调用其引用的每个方法。 在 C# 中,多播委托是通过将委托的多个实例合并成一个链来实现的。您可以使用 += 运算符将一个委托实例连接到另一个委托实例,也可以使用 -= 运算符将一个委托实例从另一个委托实例链中移除。 以下是一个简单的示例,演示了多播委托的用法:

class Program
{
    public delegate void MyDelegate(string message);

    static void Main(string[] args)
    {
        // 实例化委托,将其指向两个不同的方法
        MyDelegate delegateInstance1 = new MyDelegate(Method1);
        delegateInstance1 += Method2;

        // 将两个委托实例连接成一个多播委托
        //MyDelegate multicastDelegate = delegateInstance1 + delegateInstance2;

        // 调用多播委托,它将依次调用其引用的每个方法
        delegateInstance1("delegateInstance1 delegate is called!");

        // 从多播委托链中移除一个委托实例
        delegateInstance1 -= Method2;

        // 再次调用多播委托,它只会调用仍然在链中的委托实例
        delegateInstance1("After removing one delegate from delegateInstance1 delegate!");
    }

    static void Method1(string message)
    {
        Console.WriteLine("Method 1: " + message);
    }

    static void Method2(string message)
    {
        Console.WriteLine("Method 2: " + message);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

在 C# 中,多播委托的执行顺序是按照连接顺序来执行的,即先连接的方法会先被调用,后连接的方法会后被调用。 例如,如果你有一个多播委托 delegateInstance,它连接了两个方法 Method1 和 Method2,那么当你调用 delegateInstance() 时,首先会执行 Method1,然后执行 Method2。

移除委托实例的顺序与连接相反。使用 -= 运算符从多播委托中移除委托实例时,是从后往前,逐个匹配,如果匹配不到,就不做任何操作,如果匹配到,就把当前这个移除,且停止去继续往后匹配,在移除的方法的时候,必须是同一个实例的同一个方法才能移除,每个lambda表达式在底层会生成不同的方法名的,看起来一样实际不同。

# 事件(Event)

在 C# 中,事件是一种特殊的委托,用于实现发布者/订阅者模式。它允许一个对象(称为发布者)通知其他对象(称为订阅者或观察者)在特定的情况下发生了某些事情。

事件通常用于实现对象之间的松耦合,使得发布者不需要知道哪些对象正在监听它的事件,而订阅者也不需要知道事件是如何触发的。

在 C# 中,事件的声明如下:

public event EventHandler MyEvent;
1

这里 MyEvent 是事件的名称,EventHandler 是一个委托类型,它是 System.EventHandler 类型的实例,其定义如下:

public delegate void EventHandler(object sender, EventArgs e);
1

EventHandler 委托是一种通用的事件处理委托,它接受两个参数:一个 object 类型的参数 sender,用于指示事件的来源,以及一个 EventArgs 类型的参数 e,用于携带关于事件的信息。通常,如果事件不需要传递额外的信息,可以使用 EventArgs.Empty。

事件的使用通常包括以下几个步骤:

1、声明事件:在类中声明事件。 2、触发事件:在特定的情况下,使用事件的 Invoke 方法或简化语法 EventName() 来触发事件。 3、订阅事件:在其他类中订阅该事件,即将事件处理方法连接到事件上。 4、事件处理:当事件被触发时,订阅的事件处理方法将被调用。

以下是一个简单的示例,演示了事件的使用:

class Program
{
    public event EventHandler MyEvent;

    static void Main(string[] args)
    {
        Program program = new Program();

        // 订阅事件
        program.MyEvent += HandleEvent;

        // 触发事件
        program.OnMyEvent();
    }

    // 触发事件的方法
    protected virtual void OnMyEvent()
    {
        MyEvent?.Invoke(this, EventArgs.Empty);
    }

    // 事件处理方法
    static void HandleEvent(object sender, EventArgs e)
    {
        Console.WriteLine("Event handled!");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

在这个示例中,我们首先声明了一个名为 MyEvent 的事件。然后,在 Main 方法中,我们订阅了这个事件,并在程序中调用 OnMyEvent 方法来触发事件。当事件被触发时,事件处理方法 HandleEvent 将被调用。