一、委托(Delegate)基础
1. 委托的概念
委托是C#中的一种类型,它定义了方法的签名,可以持有对方法的引用。委托本质上是类型安全的函数指针,允许将方法作为参数传递或存储。
// 声明委托类型
public delegate void MessageHandler(string message);
// 兼容方法
public void DisplayMessage(string msg)
{
Console.WriteLine($"消息:{msg}");
}
public void LogMessage(string log)
{
File.AppendAllText("log.txt", $"{DateTime.Now}: {log}\n");
}
// 使用委托
MessageHandler handler = DisplayMessage;
handler += LogMessage; // 多播委托
handler("系统启动"); // 同时调用两个方法
2. 委托的特点
- 类型安全:编译时检查方法签名匹配
- 多播能力:一个委托实例可以引用多个方法
- 异步支持:可用于异步方法调用
- 灵活性:能够动态改变引用的方法
二、内置委托类型
.NET框架提供了几种常用的通用委托类型:
1. Action委托
表示无返回值的方法(支持0-16个参数):
Action<string, int> action = (s, i) =>
{
Console.WriteLine($"{s} {i}");
};
action("计数", 5);
2. Func委托
表示有返回值的方法(最后一个类型参数为返回值类型):
Func<int, int, int> add = (a, b) => a + b;
int result = add(3, 4); // 返回7
3. Predicate委托
表示返回bool的方法(通常用于条件判断):
Predicate<int> isEven = num => num % 2 == 0;
bool even = isEven(4); // 返回true
三、事件(Event)机制
1. 事件的基本概念
事件是基于委托的特殊机制,提供了一种发布-订阅模型,允许对象通知其他对象发生特定情况。
// 发布者类
public class TemperatureMonitor
{
// 1. 定义委托类型
public delegate void TemperatureChangedHandler(decimal newTemp);
// 2. 声明事件
public event TemperatureChangedHandler TemperatureChanged;
private decimal _currentTemp;
public decimal CurrentTemperature
{
get => _currentTemp;
set
{
if (_currentTemp != value)
{
_currentTemp = value;
// 3. 触发事件
OnTemperatureChanged(_currentTemp);
}
}
}
protected virtual void OnTemperatureChanged(decimal newTemp)
{
TemperatureChanged?.Invoke(newTemp);
}
}
// 订阅者类
public class TemperatureDisplay
{
public void Subscribe(TemperatureMonitor monitor)
{
monitor.TemperatureChanged += ShowTemperature;
}
private void ShowTemperature(decimal temp)
{
Console.WriteLine($"当前温度:{temp}°C");
}
}
// 使用
var monitor = new TemperatureMonitor();
var display = new TemperatureDisplay();
display.Subscribe(monitor);
monitor.CurrentTemperature = 23.5m; // 触发事件
2. 事件与委托的区别
特性 | 委托 | 事件 |
---|---|---|
调用权限 | 类外部可调用 | 仅声明类内部可触发 |
订阅控制 | 可赋值(=) | 只能使用+=和-=操作 |
设计目的 | 通用方法引用 | 实现观察者模式 |
封装性 | 低 | 高 |
四、标准事件模式
.NET框架定义的标准事件模式:
// 事件参数类
public class ProcessCompletedEventArgs : EventArgs
{
public bool Success { get; }
public DateTime CompletionTime { get; }
public ProcessCompletedEventArgs(bool success, DateTime completionTime)
{
Success = success;
CompletionTime = completionTime;
}
}
// 发布者
public class Processor
{
// 使用EventHandler<T>定义事件
public event EventHandler<ProcessCompletedEventArgs> ProcessCompleted;
public void StartProcess()
{
try
{
// 模拟处理过程
Thread.Sleep(2000);
// 触发事件
OnProcessCompleted(true, DateTime.Now);
}
catch
{
OnProcessCompleted(false, DateTime.Now);
}
}
protected virtual void OnProcessCompleted(bool success, DateTime time)
{
ProcessCompleted?.Invoke(this, new ProcessCompletedEventArgs(success, time));
}
}
// 订阅者
public class Logger
{
public void Subscribe(Processor processor)
{
processor.ProcessCompleted += LogCompletion;
}
private void LogCompletion(object sender, ProcessCompletedEventArgs e)
{
string status = e.Success ? "成功" : "失败";
Console.WriteLine($"处理{status},完成时间:{e.CompletionTime}");
}
}
五、匿名方法与Lambda表达式
1. 匿名方法
Button.Click += delegate(object sender, EventArgs e)
{
Console.WriteLine("按钮被点击");
};
2. Lambda表达式
Func<int, int> square = x => x * x;
Action<string> greet = name =>
{
string message = $"你好,{name}";
Console.WriteLine(message);
};
六、委托与事件的高级应用
1. 多播委托的异常处理
public delegate void WorkHandler();
public class Worker
{
public event WorkHandler WorkDone;
public void DoWork()
{
try
{
// 模拟工作
Console.WriteLine("工作中...");
// 安全调用多播委托
Delegate[] handlers = WorkDone?.GetInvocationList();
if (handlers != null)
{
foreach (WorkHandler handler in handlers)
{
try
{
handler();
}
catch (Exception ex)
{
Console.WriteLine($"处理器异常:{ex.Message}");
}
}
}
}
finally
{
Console.WriteLine("工作完成");
}
}
}
2. 弱事件模式
解决事件导致的内存泄漏问题:
using System.Windows; // 需要引用WindowsBase.dll
public class WeakEventSample
{
public class EventSource
{
public event EventHandler Event;
public void RaiseEvent()
{
Event?.Invoke(this, EventArgs.Empty);
}
}
public class EventListener
{
public void Subscribe(EventSource source)
{
WeakEventManager<EventSource, EventArgs>.AddHandler(
source,
"Event",
OnEvent);
}
private void OnEvent(object sender, EventArgs e)
{
Console.WriteLine("收到事件(弱引用)");
}
}
}
七、综合示例:股票价格监控系统
using System;
using System.Timers;
class Program
{
static void Main()
{
// 创建股票监控器
var stockMonitor = new StockMonitor("AAPL");
// 创建显示服务
var display = new StockDisplay();
var logger = new StockLogger();
// 订阅事件
stockMonitor.PriceChanged += display.ShowPrice;
stockMonitor.PriceChanged += logger.LogPrice;
stockMonitor.PriceThresholdReached += display.NotifyThreshold;
// 启动监控
stockMonitor.StartMonitoring();
Console.ReadLine(); // 保持程序运行
}
}
// 股票价格事件参数
public class StockPriceEventArgs : EventArgs
{
public string Symbol { get; }
public decimal OldPrice { get; }
public decimal NewPrice { get; }
public DateTime Time { get; }
public StockPriceEventArgs(string symbol, decimal oldPrice, decimal newPrice)
{
Symbol = symbol;
OldPrice = oldPrice;
NewPrice = newPrice;
Time = DateTime.Now;
}
}
// 股票监控器
public class StockMonitor
{
private readonly string _symbol;
private decimal _currentPrice;
private readonly Timer _timer;
private readonly Random _random = new Random();
public event EventHandler<StockPriceEventArgs> PriceChanged;
public event EventHandler<StockPriceEventArgs> PriceThresholdReached;
public StockMonitor(string symbol)
{
_symbol = symbol;
_currentPrice = 150.00m; // 初始价格
_timer = new Timer(1000); // 1秒间隔
_timer.Elapsed += CheckPrice;
}
public void StartMonitoring()
{
_timer.Start();
}
private void CheckPrice(object sender, ElapsedEventArgs e)
{
// 模拟价格变化
decimal change = (_random.NextDouble() > 0.5 ? 1 : -1) *
(decimal)_random.NextDouble() * 2m;
decimal newPrice = _currentPrice + change;
// 触发价格变化事件
OnPriceChanged(newPrice);
// 检查价格阈值(±5%)
if (Math.Abs(newPrice - 150m) / 150m >= 0.05m)
{
OnPriceThresholdReached(newPrice);
}
_currentPrice = newPrice;
}
protected virtual void OnPriceChanged(decimal newPrice)
{
var args = new StockPriceEventArgs(_symbol, _currentPrice, newPrice);
PriceChanged?.Invoke(this, args);
}
protected virtual void OnPriceThresholdReached(decimal newPrice)
{
var args = new StockPriceEventArgs(_symbol, _currentPrice, newPrice);
PriceThresholdReached?.Invoke(this, args);
}
}
// 股票显示服务
public class StockDisplay
{
public void ShowPrice(object sender, StockPriceEventArgs e)
{
Console.ForegroundColor = e.NewPrice > e.OldPrice ? ConsoleColor.Green :
e.NewPrice < e.OldPrice ? ConsoleColor.Red :
ConsoleColor.Gray;
Console.WriteLine($"{e.Time:T} {e.Symbol} 价格: {e.OldPrice:C} → {e.NewPrice:C}");
Console.ResetColor();
}
public void NotifyThreshold(object sender, StockPriceEventArgs e)
{
Console.BackgroundColor = ConsoleColor.Yellow;
Console.ForegroundColor = ConsoleColor.Black;
Console.WriteLine($"警告!{e.Symbol} 价格波动超过5%:{e.NewPrice:C}");
Console.ResetColor();
}
}
// 股票日志服务
public class StockLogger
{
public void LogPrice(object sender, StockPriceEventArgs e)
{
string log = $"{e.Time:yyyy-MM-dd HH:mm:ss} {e.Symbol} " +
$"价格变化: {e.OldPrice:C} → {e.NewPrice:C} " +
$"变化: {(e.NewPrice - e.OldPrice):C} " +
$"({(e.NewPrice - e.OldPrice)/e.OldPrice:P2})";
File.AppendAllText("stock_log.txt", log + Environment.NewLine);
}
}
八、最佳实践
- 命名规范:
- 委托类型名称以
Handler
结尾 - 事件名称使用现在时或过去时动词(如
Click
/Clicked
)
- 事件设计:
- 使用
EventHandler<T>
标准模式 - 事件参数继承自
EventArgs
- 提供受保护的
OnEventName
方法触发事件
- 内存管理:
- 及时取消订阅事件(-=)
- 考虑使用弱事件处理可能的内存泄漏
- 线程安全:
- 多线程环境下应同步事件调用
- 使用
?.Invoke()
避免null检查
- 性能考虑:
- 避免在频繁触发的事件中执行耗时操作
- 考虑使用事件聚合器处理大量事件
委托与事件是C#中实现松耦合、响应式编程的强大工具,熟练掌握它们对于开发高质量的.NET应用程序至关重要。通过合理运用这些机制,可以构建出灵活、可扩展且易于维护的事件驱动架构。