委托与事件-C# 事件驱动编程详解
委托(Delegate)是C#中的一种类型,表示对具有特定参数列表和返回类型的方法的引用。可以理解为类型安全的函数指针。// 自定义事件参数类set;set;set;// 使用自定义参数的事件// 执行订单逻辑...// 触发事件});// 使用Console.WriteLine($"订单eOrderId已下单");Console.WriteLine($"金额:eAmountC");Console
·
什么是委托
委托(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交互
- 发布订阅模式
- 对象间通信
- 状态变化通知
关键原则
- 委托:灵活但暴露实现细节
- 事件:封装性好,推荐用于对外API
- 内存管理:及时取消订阅避免泄漏
- 线程安全:多线程环境需要额外处理
- 异常处理:事件处理器不应抛出未处理异常
提示:委托和事件是C#事件驱动编程的核心,理解其原理对于设计松耦合系统至关重要。
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)