聊聊 C# 中的委托
- 什么是委托(Delegate)
- 单播委托(Unicast Delegate)
- 多播委托(Multicast Delegate)
- 内置委托(Action & Func)
- 单播委托(使用 Action 和 Func)
- 多播委托(使用 Action)
- 多播委托(使用 Func)
- 使用内置 Action 和 Func 的好处
- Action 委托
- Func 委托
- 什么情况下才需要自定义委托
- 1. 方法签名不匹配
- 2. 提高代码可读性和语义清晰性
- 3. 多播委托的需求
- 4. 事件处理
- 5. 异步操作
- 6. 泛型约束
- 总结
什么是委托(Delegate)
委托(Delegate
)是一种类型安全的函数指针,它允许你将方法作为参数传递给其他方法,或者将方法赋值给变量。委托在 C#
中用于事件处理、回调函数、多播委托等场景。
- 委托的声明
委托的声明类似于方法的声明,但不包含方法体。例如:
public delegate void MyDelegate(string message);
这个声明定义了一个名为 MyDelegate
的委托类型,它接受一个 string
类型的参数并且没有返回值。
- 委托的实例化
你可以将一个方法赋值给委托实例,只要方法的签名与委托的签名匹配。例如:
public void PrintMessage(string message)
{Console.WriteLine(message);
}MyDelegate myDelegate = new MyDelegate(PrintMessage);
myDelegate("Hello, World!");
单播委托(Unicast Delegate)
单播委托是指只能引用一个方法的委托。这是 C#
中最常见的委托类型。
示例代码:
using System;// 1. 定义委托类型
public delegate void MyDelegate(string message);public class Program
{// 定义一个方法,它的签名与委托类型匹配public static void Method1(string message){Console.WriteLine("Method1: " + message);}public static void Main(){// 2. 创建委托实例并关联方法MyDelegate del1 = new MyDelegate(Method1);// 3. 调用委托del1("Hello, World!");}
}
输出结果:
Method1: Hello, World!
解释:
- 在上面的示例中,我们首先定义了一个名为
MyDelegate
的委托类型,它接受一个string
类型的参数,没有返回值。 - 然后定义了一个方法
Method1
,它的签名与MyDelegate
匹配。 - 创建了一个委托实例
del1
,并将其关联到Method1
。 - 调用
del1
时,Method1
被执行。
多播委托(Multicast Delegate)
委托可以组合多个方法,形成一个多播委托。多播委托是指可以引用多个方法的委托。通过使用多播委托,可以将多个方法组合在一起,形成一个调用列表。
示例代码:
using System;// 1. 定义委托类型
public delegate void MyDelegate(string message);public class Program
{// 定义两个方法,它们的签名与委托类型匹配public static void Method1(string message){Console.WriteLine("Method1: " + message);}public static void Method2(string message){Console.WriteLine("Method2: " + message);}public static void Main(){// 2. 创建委托实例并关联方法MyDelegate del1 = new MyDelegate(Method1);MyDelegate del2 = new MyDelegate(Method2);// 3. 组合委托MyDelegate multicastDelegate = del1 + del2;// 4. 调用委托multicastDelegate("Hello, World!");// 5. 移除方法multicastDelegate -= del1;// 再次调用委托multicastDelegate("Hello again!");}
}
输出结果:
Method1: Hello, World!
Method2: Hello, World!
Method2: Hello again!
解释:
- 在上面的示例中,我们首先定义了一个名为
MyDelegate
的委托类型,它接受一个string
类型的参数,没有返回值。 - 然后定义了两个方法
Method1
和Method2
,它们的签名与MyDelegate
匹配。 - 创建了两个委托实例
del1
和del2
,分别关联Method1
和Method2
。 - 使用
+
运算符将del1
和del2
组合成一个多播委托multicastDelegate
。 - 调用
multicastDelegate
时,Method1
和Method2
会按顺序被调用。 - 使用
-
运算符从multicastDelegate
中移除del1
,再次调用时只有Method2
被执行。
通过这种方式,多播委托为 C#
提供了一种强大的机制来处理 事件和回调
,使得代码更加灵活和可扩展。
内置委托(Action & Func)
当然,我们可以使用 C#
内置的 Action
和 Func
委托来实现 单播和多播委托
。下面我会分别给出示例代码。
单播委托(使用 Action 和 Func)
示例代码:
using System;public class Program
{// 定义一个方法,它的签名与 Action<string> 匹配public static void PrintMessage(string message){Console.WriteLine("Message: " + message);}// 定义一个方法,它的签名与 Func<int, int, int> 匹配public static int Add(int a, int b){return a + b;}public static void Main(){// 单播委托示例:使用 Action<string>Action<string> actionDelegate = new Action<string>(PrintMessage);actionDelegate("Hello, World!");// 单播委托示例:使用 Func<int, int, int>Func<int, int, int> funcDelegate = new Func<int, int, int>(Add);int result = funcDelegate(3, 4);Console.WriteLine("Result from funcDelegate: " + result);}
}
输出结果:
Message: Hello, World!
Result from funcDelegate: 7
解释:
- 使用
Action<string>
委托来实现单播委托,关联PrintMessage
方法。 - 使用
Func<int, int, int>
委托来实现单播委托,关联Add
方法。 - 调用这些委托时,分别执行关联的方法。
多播委托(使用 Action)
Action
委托在 C#
中通常用于表示无返回值的方法。它是一组预定义的委托类型之一,旨在简化代码编写并提高可读性。
示例代码:
using System;public class Program
{// 定义两个方法,它们的签名与 Action<string> 匹配public static void PrintMessage1(string message){Console.WriteLine("Message1: " + message);}public static void PrintMessage2(string message){Console.WriteLine("Message2: " + message);}public static void Main(){// 创建 Action<string> 委托实例并关联方法Action<string> actionDelegate1 = new Action<string>(PrintMessage1);Action<string> actionDelegate2 = new Action<string>(PrintMessage2);// 组合委托Action<string> multicastDelegate = actionDelegate1 + actionDelegate2;// 调用委托multicastDelegate("Hello, World!");// 移除方法multicastDelegate -= actionDelegate1;// 再次调用委托multicastDelegate("Hello again!");}
}
输出结果:
Message1: Hello, World!
Message2: Hello, World!
Message2: Hello again!
解释:
- 使用
Action<string>
委托来实现多播委托,关联PrintMessage1
和PrintMessage2
方法。 - 使用
+
运算符将actionDelegate1
和actionDelegate2
组合成一个多播委托multicastDelegate
。 - 调用
multicastDelegate
时,PrintMessage1
和PrintMessage2
会按顺序被调用。 - 使用
-
运算符从multicastDelegate
中移除actionDelegate1
,再次调用时只有PrintMessage2
被执行。
多播委托(使用 Func)
Func
委托通常用于返回值,因此多播委托的使用场景较少。不过,我们可以通过组合多个 Func
委托来实现类似的效果,但需要注意的是,Func
委托的返回值会覆盖之前的返回值。
示例代码:
using System;public class Program
{// 定义两个方法,它们的签名与 Func<int, int> 匹配public static int Increment(int value){return value + 1;}public static int Double(int value){return value * 2;}public static void Main(){// 创建 Func<int, int> 委托实例并关联方法Func<int, int> funcDelegate1 = new Func<int, int>(Increment);Func<int, int> funcDelegate2 = new Func<int, int>(Double);// 组合委托Func<int, int> multicastFunc = funcDelegate1 + funcDelegate2;// 调用委托// 注意:Func 的多播委托调用会覆盖之前的返回值int result = multicastFunc(3);Console.WriteLine("Result from multicastFunc: " + result);}
}
输出结果:
Unhandled Exception: System.MulticastNotSupportedException: Multicast delegate invocation with non-void return type is not allowed.
解释:
- 使用
Func<int, int>
委托来尝试实现多播委托,关联Increment
和Double
方法。 - 尝试使用
+
运算符将funcDelegate1
和funcDelegate2
组合成一个多播委托multicastFunc
。 - 调用
multicastFunc
时,会抛出MulticastNotSupportedException
异常,因为Func
委托的多播调用不支持非void
返回类型。
使用内置 Action 和 Func 的好处
Action 委托
Action
委托是 C#
提供的内置委托类型,用于表示没有返回值的方法。Action
委托有多个重载版本,可以接受 0
到 16
个参数。
Func 委托
Func
委托是 C#
提供的内置委托类型,用于表示有返回值的方法。Func
委托有多个重载版本,可以接受 0
到 16
个参数,并且最后一个参数是返回值类型。
推荐使用他们的好处:
- 简洁性:使用
Action & Func
可以避免显式声明委托类型,使代码更简洁。 - 类型安全:
Action & Func
是类型安全的函数指针,允许你将方法作为参数传递或赋值给变量,编译器会检查参数类型和数量。 - 内置支持:
Action & Func
是C#
标准库的一部分,无需额外定义。
他们的区别:
Action
:用于表示 没有返回值的方法,有多个重载版本,可以接受0
到16
个参数。Func
:用于表示 有返回值的方法,有多个重载版本,可以接受0
到16
个参数,并且最后一个参数是返回值类型。
什么情况下才需要自定义委托
自定义委托在某些特定情况下是非常有用的,尤其是在现有的内置委托(如 Action
和 Func
)无法满足需求时。以下是需要自定义委托的一些常见场景:
1. 方法签名不匹配
当你的方法签名与现有的 Action
或 Func
委托不匹配时,你需要定义一个自定义委托。例如,如果你有一个方法接受特定类型的参数并返回特定类型的值,而这些类型不是通用的或数量超过 Func 和 Action
支持的最大参数数量(16
个),那么你需要自定义委托。
示例:
public delegate bool CustomDelegate(int x, string y, double z);
2. 提高代码可读性和语义清晰性
有时为了提高代码的可读性和语义清晰性,即使方法签名可以使用 Action
或 Func
,你仍然可以选择定义一个自定义委托。通过给委托一个有意义的名字,可以使代码更具描述性。
public delegate void EventHandler(object sender, EventArgs e);// 使用自定义委托使代码更易读
public event EventHandler OnDataReceived;
3. 多播委托的需求
当你需要将多个方法组合成一个多播委托,并且希望这些方法具有特定的签名时,自定义委托可以提供更好的控制和灵活性。
示例:
public delegate void NotificationHandler(string message);NotificationHandler handler = null;void RegisterNotifications()
{handler += HandleNotification1;handler += HandleNotification2;
}void HandleNotification1(string message)
{Console.WriteLine("Handler 1: " + message);
}void HandleNotification2(string message)
{Console.WriteLine("Handler 2: " + message);
}
4. 事件处理
在 C#
中,事件通常使用委托来定义。虽然你可以使用 EventHandler
或 EventHandler<TEventArgs>
,但在某些情况下,你可能需要定义一个更具体的委托来处理特定类型的事件。
示例:
public class DataChangedEventArgs : EventArgs
{public int OldValue { get; }public int NewValue { get; }public DataChangedEventArgs(int oldValue, int newValue){OldValue = oldValue;NewValue = newValue;}
}public delegate void DataChangedEventHandler(object sender, DataChangedEventArgs e);public class DataSource
{public event DataChangedEventHandler DataChanged;protected virtual void OnDataChanged(DataChangedEventArgs e){DataChanged?.Invoke(this, e);}
}
5. 异步操作
对于复杂的异步操作,特别是那些涉及回调函数的情况,自定义委托可以帮助更好地组织和管理代码。
示例:
public delegate void AsyncOperationCompletedHandler(bool success, string result);public class AsyncService
{public void PerformAsyncOperation(AsyncOperationCompletedHandler callback){// 模拟异步操作Task.Run(() =>{// 操作完成后调用回调callback(true, "Operation completed successfully");});}
}
6. 泛型约束
有时你需要对委托中的泛型参数施加约束,这在 Func 和 Action 中是无法直接实现的。通过自定义委托,你可以添加泛型约束以确保类型安全。
示例:
public delegate TResult CustomGenericDelegate<in T, out TResult>(T arg) where T : class;
通过自定义委托,你可以更灵活地处理各种编程需求,同时保持代码的清晰性和可维护性。如果你发现现有的 Action
和 Func
委托已经足够满足需求(通常大多数情况下内置委托类型已经足够需求),则无需额外定义自定义委托。
总结
Action
和Func
是C#
中非常方便的委托类型,可以简化委托的定义和使用。Action
适用于没有返回值的方法。Func
适用于有返回值的方法。- 多播委托通常使用
Action
来实现,而Func
由于其返回值特性,多播调用不常用。
希望这些示例能帮助你更好地理解如何使用 Action
和 Func
来实现单播和多播委托。