1. 委托的本质与核心价值
委托(Delegate)是.NET框架中实现事件驱动编程的核心机制,本质上是一种类型安全的函数指针。它允许我们将方法作为参数传递,实现回调功能。与直接调用方法不同,委托提供了间接调用方法的途径,这种间接性正是其强大之处。
在C#中,委托是类类型(class type),继承自System.MulticastDelegate。每个委托类型都定义了特定的方法签名,只有匹配该签名的方法才能被委托实例引用。这种强类型检查避免了C/C++函数指针可能出现的类型安全问题。
关键理解:委托不是方法,而是持有方法引用的对象。当调用委托时,实际上是通过委托对象间接调用其引用的方法。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 委托的声明与使用
2.1 基本委托类型
C#内置了两种通用委托类型:
- Action:无返回值的方法委托
- Func:有返回值的方法委托
csharp复制// 自定义委托声明
public delegate int CalculateDelegate(int x, int y);
// 使用内置Action委托
Action<string> logAction = message => Console.WriteLine(message);
// 使用内置Func委托
Func<int, int, int> addFunc = (a, b) => a + b;
2.2 委托实例化与调用
委托实例化有三种常见方式:
- 方法组转换
- 匿名方法
- Lambda表达式
csharp复制// 方法组转换
int Add(int a, int b) => a + b;
CalculateDelegate calc = Add;
// 匿名方法
CalculateDelegate calc2 = delegate(int a, int b) { return a * b; };
// Lambda表达式
CalculateDelegate calc3 = (a, b) => a - b;
// 委托调用
int result = calc(10, 5); // 输出15
3. 多播委托与事件机制
3.1 多播委托原理
委托的一个重要特性是支持多播(Multicast),即一个委托实例可以引用多个方法。当调用多播委托时,这些方法会按添加顺序依次执行。
csharp复制Action multiAction = () => Console.Write("A");
multiAction += () => Console.Write("B");
multiAction += () => Console.Write("C");
multiAction(); // 输出"ABC"
注意事项:多播委托的返回值通常是最后一个方法的返回值,前面的返回值会被丢弃。因此实践中多用于返回void的方法。
3.2 事件与委托的关系
事件是建立在委托基础上的语法糖,本质上是特殊的委托字段。事件提供了更安全的封装:
csharp复制public class Publisher
{
// 1. 定义委托类型
public delegate void NotifyHandler(string message);
// 2. 声明事件
public event NotifyHandler OnNotify;
public void RaiseEvent()
{
OnNotify?.Invoke("Event raised!");
}
}
事件与普通委托字段的关键区别:
- 事件只能在声明它的类中触发
- 外部只能通过+=/-=操作符订阅/取消订阅
- 防止外部直接重置委托链
4. 委托的高级应用场景
4.1 回调机制实现
委托最常见的用途是实现回调。例如在异步编程中:
csharp复制void LongRunningOperation(Action<string> callback)
{
// 模拟耗时操作
Thread.Sleep(2000);
callback("Operation completed");
}
// 使用回调
LongRunningOperation(message => Console.WriteLine(message));
4.2 策略模式实现
委托可以简化策略模式的实现:
csharp复制public class Sorter
{
private Func<int[], int[]> _sortStrategy;
public void SetSortStrategy(Func<int[], int[]> strategy)
{
_sortStrategy = strategy;
}
public int[] Sort(int[] data)
{
return _sortStrategy?.Invoke(data) ?? data;
}
}
// 使用不同的排序策略
var sorter = new Sorter();
sorter.SetSortStrategy(data => data.OrderBy(x => x).ToArray());
4.3 LINQ的基石
LINQ的核心就是基于委托实现的。例如Where方法的简化实现:
csharp复制public static IEnumerable<TSource> Where<TSource>(
this IEnumerable<TSource> source,
Func<TSource, bool> predicate)
{
foreach (var item in source)
{
if (predicate(item))
yield return item;
}
}
5. 性能优化与最佳实践
5.1 委托性能考量
委托调用比直接方法调用稍慢(约2-3倍),但在大多数场景下差异可以忽略。需要关注的是:
- 避免在热路径(hot path)中频繁创建委托实例
- 对于重复使用的委托,考虑缓存委托实例
- 多播委托的调用时间与注册方法数量成正比
5.2 内存管理注意事项
委托可能导致意外的对象生命周期延长,因为委托会持有目标对象的引用:
csharp复制var processor = new DataProcessor();
SomeClass.EventHandler += processor.HandleData; // processor不会被GC回收
// 正确的做法是在不需要时取消订阅
SomeClass.EventHandler -= processor.HandleData;
5.3 线程安全实践
委托调用默认不是线程安全的。在多线程环境下:
csharp复制// 不安全的做法
public event EventHandler UnsafeEvent;
// 安全的做法
public event EventHandler SafeEvent;
protected virtual void OnSafeEvent()
{
var handler = SafeEvent;
handler?.Invoke(this, EventArgs.Empty);
}
6. C#委托的演进与新特性
6.1 协变与逆变支持
C# 4.0开始,委托支持泛型类型参数的协变(covariance)和
