什么是委托

委托(Delegate)是C#中的一种类型,表示对具有特定参数列表和返回类型的方法的引用。可以理解为类型安全的函数指针。

委托基础

声明和使用委托

// 声明委托类型
public delegate int Calculate(int x, int y);

public class Calculator
{
    // 匹配委托签名的方法
    public int Add(int x, int y) => x + y;
    public int Subtract(int x, int y) => x - y;
    public int Multiply(int x, int y) => x * y;
}

// 使用示例
var calc = new Calculator();

// 创建委托实例
Calculate operation = calc.Add;
int result = operation(10, 5);  // 15

// 切换委托指向的方法
operation = calc.Subtract;
result = operation(10, 5);      // 5

委托的本质

// 委托是一个类
public delegate void MyDelegate(string message);

// 等价于(编译器生成)
public class MyDelegate : MulticastDelegate
{
    public MyDelegate(object target, IntPtr method);
    public void Invoke(string message);
    public IAsyncResult BeginInvoke(string message, AsyncCallback callback, object state);
    public void EndInvoke(IAsyncResult result);
}

内置委托类型

Action - 无返回值

// 无参数
Action greet = () => Console.WriteLine("Hello");
greet();

// 1个参数
Action<string> print = msg => Console.WriteLine(msg);
print("Hello World");

// 2个参数
Action<string, int> printWithCount = (msg, count) => 
{
    for (int i = 0; i < count; i++)
        Console.WriteLine(msg);
};
printWithCount("Hi", 3);

// 最多16个参数
Action<int, int, int, int> fourParams = (a, b, c, d) => 
{
    Console.WriteLine(a + b + c + d);
};

Func - 有返回值

// 无参数,返回int
Func<int> getRandom = () => new Random().Next();
int number = getRandom();

// 1个参数,返回string
Func<int, string> toString = x => x.ToString();
string text = toString(42);

// 2个参数,返回int
Func<int, int, int> add = (x, y) => x + y;
int sum = add(10, 20);

// 最后一个类型参数是返回值类型
Func<string, int, bool> checkLength = (str, len) => str.Length > len;
bool isLong = checkLength("Hello", 3);  // true

Predicate - 返回bool的委托

// 用于条件判断
Predicate<int> isEven = x => x % 2 == 0;
bool result = isEven(4);  // true

// List<T> 中的应用
var numbers = new List<int> { 1, 2, 3, 4, 5, 6 };
var evenNumbers = numbers.FindAll(isEven);
// 结果: [2, 4, 6]

多播委托

委托链

public delegate void Notify(string message);

public class NotificationSystem
{
    public void EmailNotify(string msg) => Console.WriteLine($"Email: {msg}");
    public void SmsNotify(string msg) => Console.WriteLine($"SMS: {msg}");
    public void PushNotify(string msg) => Console.WriteLine($"Push: {msg}");
}

// 使用多播委托
var notifier = new NotificationSystem();
Notify notify = notifier.EmailNotify;
notify += notifier.SmsNotify;    // 添加方法
notify += notifier.PushNotify;

// 调用时会依次执行所有方法
notify("系统升级通知");
// 输出:
// Email: 系统升级通知
// SMS: 系统升级通知
// Push: 系统升级通知

// 移除方法
notify -= notifier.SmsNotify;

多播委托返回值

public delegate int Operation(int x);

Operation ops = x => x + 1;
ops += x => x * 2;
ops += x => x - 3;

// 只返回最后一个委托的结果
int result = ops(5);  // 返回 2((5-3)是最后一个)

获取所有委托结果

public delegate int Calculate(int x);

Calculate calc = x => x + 1;
calc += x => x * 2;
calc += x => x * 3;

// 获取所有委托的返回值
foreach (Calculate del in calc.GetInvocationList())
{
    int result = del(5);
    Console.WriteLine(result);
}
// 输出: 6, 10, 15

事件(Event)

事件基础

public class Button
{
    // 声明事件(基于委托)
    public event EventHandler Clicked;
    
    public void Click()
    {
        // 触发事件(推荐方式)
        Clicked?.Invoke(this, EventArgs.Empty);
    }
}

// 使用事件
var button = new Button();

// 订阅事件
button.Clicked += (sender, e) => 
{
    Console.WriteLine("按钮被点击了!");
};

// 触发
button.Click();

自定义事件参数

// 自定义事件参数类
public class OrderEventArgs : EventArgs
{
    public int OrderId { get; set; }
    public decimal Amount { get; set; }
    public DateTime OrderTime { get; set; }
}

public class OrderService
{
    // 使用自定义参数的事件
    public event EventHandler<OrderEventArgs> OrderPlaced;
    
    public void PlaceOrder(int orderId, decimal amount)
    {
        // 执行订单逻辑...
        
        // 触发事件
        OrderPlaced?.Invoke(this, new OrderEventArgs
        {
            OrderId = orderId,
            Amount = amount,
            OrderTime = DateTime.Now
        });
    }
}

// 使用
var orderService = new OrderService();

orderService.OrderPlaced += (sender, e) =>
{
    Console.WriteLine($"订单 {e.OrderId} 已下单");
    Console.WriteLine($"金额: {e.Amount:C}");
    Console.WriteLine($"时间: {e.OrderTime}");
};

orderService.PlaceOrder(1001, 299.99m);

委托与事件的区别

委托可以直接调用

public class Example
{
    // 委托字段
    public Action OnAction;
    
    // 事件
    public event Action OnEvent;
}

var example = new Example();

// 委托可以被外部直接调用和赋值
example.OnAction = () => Console.WriteLine("Action");
example.OnAction();  // ✅ 可以

// 事件只能 += 和 -=
example.OnEvent += () => Console.WriteLine("Event");
// example.OnEvent();  // ❌ 编译错误:只能在类内部调用
// example.OnEvent = null;  // ❌ 编译错误:只能 += 或 -=

事件的封装性更好

public class Publisher
{
    private EventHandler _myDelegate;  // 委托字段
    public event EventHandler MyEvent;  // 事件
    
    public void TestAccess()
    {
        // 类内部可以调用事件
        MyEvent?.Invoke(this, EventArgs.Empty);
    }
}

// 外部代码
var publisher = new Publisher();

// 委托:可以被外部完全控制
publisher._myDelegate = null;  // 清空所有订阅者
publisher._myDelegate?.Invoke(publisher, EventArgs.Empty);  // 外部触发

// 事件:外部只能订阅和取消订阅
publisher.MyEvent += Handler;  // ✅
publisher.MyEvent -= Handler;  // ✅
// publisher.MyEvent = null;  // ❌ 编译错误
// publisher.MyEvent?.Invoke(...);  // ❌ 编译错误

实战场景

场景1:观察者模式

public class StockMarket
{
    private decimal _price;
    
    // 价格变化事件
    public event EventHandler<PriceChangedEventArgs> PriceChanged;
    
    public decimal Price
    {
        get => _price;
        set
        {
            if (_price != value)
            {
                decimal oldPrice = _price;
                _price = value;
                
                // 触发事件
                OnPriceChanged(new PriceChangedEventArgs
                {
                    OldPrice = oldPrice,
                    NewPrice = value,
                    ChangePercent = ((value - oldPrice) / oldPrice) * 100
                });
            }
        }
    }
    
    protected virtual void OnPriceChanged(PriceChangedEventArgs e)
    {
        PriceChanged?.Invoke(this, e);
    }
}

public class PriceChangedEventArgs : EventArgs
{
    public decimal OldPrice { get; set; }
    public decimal NewPrice { get; set; }
    public decimal ChangePercent { get; set; }
}

// 使用
var stock = new StockMarket();

// 监控者1:日志记录
stock.PriceChanged += (sender, e) =>
{
    Console.WriteLine($"价格从 {e.OldPrice:C} 变为 {e.NewPrice:C}");
    Console.WriteLine($"涨跌幅: {e.ChangePercent:F2}%");
};

// 监控者2:价格预警
stock.PriceChanged += (sender, e) =>
{
    if (e.ChangePercent > 5)
        Console.WriteLine("警告:价格波动超过5%!");
};

stock.Price = 100m;  // 触发事件
stock.Price = 110m;  // 触发事件

场景2:进度报告

public class FileDownloader
{
    public event EventHandler<ProgressEventArgs> ProgressChanged;
    public event EventHandler DownloadCompleted;
    
    public async Task DownloadAsync(string url, string destination)
    {
        using var client = new HttpClient();
        using var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
        
        var totalBytes = response.Content.Headers.ContentLength ?? 0;
        var buffer = new byte[8192];
        long totalRead = 0;
        
        using var contentStream = await response.Content.ReadAsStreamAsync();
        using var fileStream = File.Create(destination);
        
        int bytesRead;
        while ((bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length)) > 0)
        {
            await fileStream.WriteAsync(buffer, 0, bytesRead);
            totalRead += bytesRead;
            
            // 报告进度
            var progress = (int)((totalRead * 100) / totalBytes);
            ProgressChanged?.Invoke(this, new ProgressEventArgs
            {
                BytesReceived = totalRead,
                TotalBytes = totalBytes,
                ProgressPercentage = progress
            });
        }
        
        DownloadCompleted?.Invoke(this, EventArgs.Empty);
    }
}

public class ProgressEventArgs : EventArgs
{
    public long BytesReceived { get; set; }
    public long TotalBytes { get; set; }
    public int ProgressPercentage { get; set; }
}

// 使用
var downloader = new FileDownloader();

downloader.ProgressChanged += (sender, e) =>
{
    Console.WriteLine($"进度: {e.ProgressPercentage}% ({e.BytesReceived}/{e.TotalBytes} 字节)");
};

downloader.DownloadCompleted += (sender, e) =>
{
    Console.WriteLine("下载完成!");
};

await downloader.DownloadAsync("https://example.com/file.zip", "file.zip");

场景3:UI按钮事件

public class Button
{
    public event EventHandler Click;
    public event EventHandler<MouseEventArgs> MouseEnter;
    public event EventHandler<MouseEventArgs> MouseLeave;
    
    public void OnClick()
    {
        Click?.Invoke(this, EventArgs.Empty);
    }
}

public class MouseEventArgs : EventArgs
{
    public int X { get; set; }
    public int Y { get; set; }
}

// 使用
var saveButton = new Button();

saveButton.Click += (sender, e) =>
{
    Console.WriteLine("保存数据...");
};

saveButton.Click += (sender, e) =>
{
    Console.WriteLine("显示保存成功提示");
};

saveButton.MouseEnter += (sender, e) =>
{
    Console.WriteLine("鼠标进入按钮");
};

场景4:事件聚合器(解耦组件)

public class EventAggregator
{
    private readonly Dictionary<Type, List<Delegate>> _events = new();
    
    public void Subscribe<T>(Action<T> handler)
    {
        var eventType = typeof(T);
        
        if (!_events.ContainsKey(eventType))
            _events[eventType] = new List<Delegate>();
        
        _events[eventType].Add(handler);
    }
    
    public void Unsubscribe<T>(Action<T> handler)
    {
        var eventType = typeof(T);
        
        if (_events.ContainsKey(eventType))
            _events[eventType].Remove(handler);
    }
    
    public void Publish<T>(T eventData)
    {
        var eventType = typeof(T);
        
        if (_events.ContainsKey(eventType))
        {
            foreach (var handler in _events[eventType].Cast<Action<T>>())
            {
                handler(eventData);
            }
        }
    }
}

// 定义事件
public class OrderCreatedEvent
{
    public int OrderId { get; set; }
    public decimal Amount { get; set; }
}

// 使用
var eventAggregator = new EventAggregator();

// 订阅者1:发送邮件
eventAggregator.Subscribe<OrderCreatedEvent>(e =>
{
    Console.WriteLine($"发送邮件:订单 {e.OrderId} 已创建");
});

// 订阅者2:更新库存
eventAggregator.Subscribe<OrderCreatedEvent>(e =>
{
    Console.WriteLine($"更新库存:订单金额 {e.Amount:C}");
});

// 发布事件
eventAggregator.Publish(new OrderCreatedEvent 
{ 
    OrderId = 1001, 
    Amount = 299.99m 
});

委托的高级用法

委托协变和逆变

// 协变(返回值)
public delegate object Factory();

string CreateString() => "Hello";
Factory factory = CreateString;  // ✅ string 可以转换为 object

// 逆变(参数)
public delegate void Process(string text);

void ProcessObject(object obj) => Console.WriteLine(obj);
Process process = ProcessObject;  // ✅ object 可以接收 string
process("Hello");

闭包

public Func<int, int> CreateMultiplier(int factor)
{
    // factor 被闭包捕获
    return x => x * factor;
}

var double = CreateMultiplier(2);
var triple = CreateMultiplier(3);

Console.WriteLine(double(5));  // 10
Console.WriteLine(triple(5));  // 15

委托缓存

public class Calculator
{
    private readonly Dictionary<string, Func<int, int, int>> _operations = new();
    
    public Calculator()
    {
        // 缓存委托避免重复创建
        _operations["add"] = (x, y) => x + y;
        _operations["subtract"] = (x, y) => x - y;
        _operations["multiply"] = (x, y) => x * y;
    }
    
    public int Calculate(string operation, int x, int y)
    {
        if (_operations.TryGetValue(operation, out var func))
            return func(x, y);
        
        throw new ArgumentException("Unknown operation");
    }
}

异步委托

BeginInvoke / EndInvoke(已过时)

public delegate int LongRunning(int x);

LongRunning del = x =>
{
    Thread.Sleep(2000);
    return x * 2;
};

// 异步调用(不推荐,已过时)
IAsyncResult result = del.BeginInvoke(10, null, null);
// ... 做其他事情
int value = del.EndInvoke(result);

推荐:使用 Task

Func<int, int> operation = x =>
{
    Thread.Sleep(2000);
    return x * 2;
};

// 使用 Task.Run
var task = Task.Run(() => operation(10));
int result = await task;

事件最佳实践

1. 事件命名规范

public class Example
{
    // 事件名使用动词过去式
    public event EventHandler Started;
    public event EventHandler Stopped;
    public event EventHandler<DataEventArgs> DataReceived;
    
    // 触发方法使用 On + 事件名
    protected virtual void OnStarted()
    {
        Started?.Invoke(this, EventArgs.Empty);
    }
}

2. 线程安全的事件触发

public class SafeEventExample
{
    private EventHandler _myEvent;
    
    public event EventHandler MyEvent
    {
        add { _myEvent += value; }
        remove { _myEvent -= value; }
    }
    
    protected virtual void OnMyEvent()
    {
        // 创建副本避免竞态条件
        var handler = _myEvent;
        handler?.Invoke(this, EventArgs.Empty);
    }
}

3. 内存泄漏防范

public class Publisher
{
    public event EventHandler DataChanged;
}

public class Subscriber
{
    private Publisher _publisher;
    
    public Subscriber(Publisher publisher)
    {
        _publisher = publisher;
        _publisher.DataChanged += OnDataChanged;
    }
    
    private void OnDataChanged(object sender, EventArgs e)
    {
        // 处理事件
    }
    
    // 实现 IDisposable 取消订阅
    public void Dispose()
    {
        if (_publisher != null)
        {
            _publisher.DataChanged -= OnDataChanged;
            _publisher = null;
        }
    }
}

4. 弱事件模式

public class WeakEventManager<TEventArgs> where TEventArgs : EventArgs
{
    private readonly List<WeakReference> _listeners = new();
    
    public void AddListener(EventHandler<TEventArgs> listener)
    {
        _listeners.Add(new WeakReference(listener));
    }
    
    public void RemoveListener(EventHandler<TEventArgs> listener)
    {
        _listeners.RemoveAll(wr => 
        {
            var target = wr.Target as EventHandler<TEventArgs>;
            return target == null || target == listener;
        });
    }
    
    public void RaiseEvent(object sender, TEventArgs e)
    {
        var deadReferences = new List<WeakReference>();
        
        foreach (var wr in _listeners)
        {
            if (wr.Target is EventHandler<TEventArgs> handler)
            {
                handler(sender, e);
            }
            else
            {
                deadReferences.Add(wr);
            }
        }
        
        // 清理死引用
        foreach (var wr in deadReferences)
        {
            _listeners.Remove(wr);
        }
    }
}

常见错误

错误1:忘记检查 null

public class BadExample
{
    public event EventHandler MyEvent;
    
    public void TriggerEvent()
    {
        // ❌ 如果没有订阅者会抛 NullReferenceException
        MyEvent(this, EventArgs.Empty);
    }
}

public class GoodExample
{
    public event EventHandler MyEvent;
    
    public void TriggerEvent()
    {
        // ✅ 使用 null 条件运算符
        MyEvent?.Invoke(this, EventArgs.Empty);
    }
}

错误2:事件处理器中抛出异常

public void TriggerEvent()
{
    try
    {
        foreach (EventHandler handler in MyEvent.GetInvocationList())
        {
            try
            {
                handler(this, EventArgs.Empty);
            }
            catch (Exception ex)
            {
                // 记录异常但继续执行其他处理器
                Console.WriteLine($"事件处理器异常: {ex.Message}");
            }
        }
    }
    catch
    {
        // 处理 GetInvocationList 可能的异常
    }
}

总结

委托使用场景

  • 回调函数
  • LINQ查询
  • 多线程委托
  • 策略模式实现

事件使用场景

  • UI交互
  • 发布订阅模式
  • 对象间通信
  • 状态变化通知

关键原则

  1. 委托:灵活但暴露实现细节
  2. 事件:封装性好,推荐用于对外API
  3. 内存管理:及时取消订阅避免泄漏
  4. 线程安全:多线程环境需要额外处理
  5. 异常处理:事件处理器不应抛出未处理异常

提示:委托和事件是C#事件驱动编程的核心,理解其原理对于设计松耦合系统至关重要。

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐