C# 委托和事件
本章将详细介绍 C# 中的委托(Delegate)、Lambda 表达式和事件(Event)。
委托概述
什么是委托?
委托是一种类型,它定义了方法的签名,可以将方法作为参数传递或存储。委托类似于 C++ 中的函数指针,但它是类型安全的。
// 委托定义
delegate int Calculate(int a, int b);
// 使用委托
Calculate add = (a, b) => a + b;
int result = add(3, 4); // 7
委托的定义和使用
定义委托
// 无返回值的委托
delegate void MessageHandler(string message);
// 有返回值的委托
delegate int SumHandler(int a, int b, int c);
// 带多个参数的委托
delegate bool FilterHandler(int number);
// 可以指向任何匹配签名的方法
使用委托
// 定义方法
int Add(int a, int b) => a + b;
int Multiply(int a, int b) => a * b;
// 创建委托实例
Calculate calc1 = Add;
Calculate calc2 = Multiply;
// 调用
Console.WriteLine(calc1(3, 4)); // 7
Console.WriteLine(calc2(3, 4)); // 12
委托作为参数
delegate void VisitHandler(int value);
class DataProcessor
{
public void Process(int[] numbers, VisitHandler visitor)
{
foreach (var n in numbers)
{
visitor(n);
}
}
}
// 使用
DataProcessor processor = new DataProcessor();
int[] data = { 1, 2, 3, 4, 5 };
processor.Process(data, n => Console.WriteLine(n * 2));
多播委托
delegate void Logger(string message);
class LoggerManager
{
public static void LogToConsole(string msg)
{
Console.WriteLine($"[控制台] {msg}");
}
public static void LogToFile(string msg)
{
Console.WriteLine($"[文件] {msg}");
}
}
// 组合委托
Logger logger = LoggerManager.LogToConsole;
logger += LoggerManager.LogToFile; // 添加另一个方法
// 调用时所有方法都会执行
logger("系统启动");
/*
输出:
[控制台] 系统启动
[文件] 系统启动
*/
匿名方法
// 使用匿名方法
delegate void Action(string s);
Action action = delegate(string s)
{
Console.WriteLine(s);
};
action("Hello");
// 可以省略参数类型(编译器推断)
Action action2 = delegate(s)
{
Console.WriteLine(s);
};
Lambda 表达式
基本语法
// 完整语法
Func<int, int, int> add = (int a, int b) => { return a + b; };
// 简化的 return 语句
Func<int, int, int> add2 = (a, b) => a + b;
// 单参数
Func<int, int> square = x => x * x;
// 无参数
Action greet = () => Console.WriteLine("Hello!");
在 LINQ 中使用
int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
// 过滤
var evens = numbers.Where(n => n % 2 == 0);
// 转换
var squares = numbers.Select(n => n * n);
// 组合
var result = numbers
.Where(n => n > 3)
.Select(n => n * 2)
.OrderBy(n => n);
变量捕获
int factor = 10;
// Lambda 捕获外部变量
Func<int, int> multiplier = x => x * factor;
Console.WriteLine(multiplier(5)); // 50
factor = 20;
Console.WriteLine(multiplier(5)); // 100(修改也会影响)
事件概述
什么是事件?
事件是一种机制,允许对象在发生某些事情时通知其他对象。事件基于委托,实现了发布-订阅模式。
┌─────────────────┐ 触发 ┌─────────────────┐
│ 发布者 │ ──────────────▶ │ 订阅者 │
│ (Publisher) │ │ (Subscriber) │
└─────────────────┘ └─────────────────┘
│
│ event
▼
┌─────────────────┐
│ 事件本身 │
│ (Delegate) │
└─────────────────┘
事件的定义
class Button
{
// 定义事件
public event EventHandler Click;
// 触发事件的方法
public void OnClick()
{
// 检查是否有订阅者
Click?.Invoke(this, EventArgs.Empty);
}
}
订阅事件
Button button = new Button();
// 订阅事件
button.Click += (s, e) => Console.WriteLine("按钮被点击!");
// 触发事件
button.OnClick();
// 取消订阅
button.Click -= handler;
完整的事件示例
发布者类
class Stock
{
private string _symbol;
private decimal _price;
// 事件参数类
public class PriceChangedEventArgs : EventArgs
{
public string Symbol { get; }
public decimal OldPrice { get; }
public decimal NewPrice { get; }
public PriceChangedEventArgs(string symbol, decimal oldPrice, decimal newPrice)
{
Symbol = symbol;
OldPrice = oldPrice;
NewPrice = newPrice;
}
}
// 定义事件
public event EventHandler<PriceChangedEventArgs>? PriceChanged;
public decimal Price
{
get => _price;
set
{
if (_price != value)
{
var oldPrice = _price;
_price = value;
OnPriceChanged(new PriceChangedEventArgs(_symbol, oldPrice, value));
}
}
}
public Stock(string symbol, decimal price)
{
_symbol = symbol;
_price = price;
}
protected virtual void OnPriceChanged(PriceChangedEventArgs e)
{
PriceChanged?.Invoke(this, e);
}
}
订阅者类
class Investor
{
public string Name { get; }
public Investor(string name)
{
Name = name;
}
public void OnPriceChanged(object? sender, Stock.PriceChangedEventArgs e)
{
var change = e.NewPrice - e.OldPrice;
var direction = change > 0 ? "上涨" : "下跌";
Console.WriteLine($"{Name} 收到通知: {e.Symbol} {direction} {Math.Abs(change):C2}");
}
}
使用
Stock apple = new Stock("AAPL", 150m);
Investor john = new Investor("John");
Investor jane = new Investor("Jane");
// 订阅事件
apple.PriceChanged += john.OnPriceChanged;
apple.PriceChanged += jane.OnPriceChanged;
// 价格变化
apple.Price = 155m;
apple.Price = 160m;
apple.Price = 158m;
内置委托类型
Action
// 无返回值的委托
Action action = () => Console.WriteLine("Hello");
Action<string> print = s => Console.WriteLine(s);
Action<int, int> add = (a, b) => Console.WriteLine(a + b);
action.Invoke();
print("World");
add(3, 4);
Func
// 有返回值的委托
Func<int> getNumber = () => 42;
Func<int, int> square = x => x * x;
Func<int, int, string> format = (a, b) => $"{a} + {b} = {a + b}";
Console.WriteLine(getNumber()); // 42
Console.WriteLine(square(5)); // 25
Console.WriteLine(format(3, 4)); // 3 + 4 = 7
Predicate
// 返回 bool 的委托
Predicate<int> isEven = n => n % 2 == 0;
Predicate<string> isLong = s => s.Length > 10;
Console.WriteLine(isEven(4)); // True
Console.WriteLine(isLong("Hello World")); // True
事件模式
标准 .NET 事件模式
// 1. 定义继承自 EventArgs 的事件参数类
public class MyEventArgs : EventArgs
{
public string Message { get; }
public MyEventArgs(string message) => Message = message;
}
// 2. 定义事件(遵循约定)
public class MyPublisher
{
// 受保护、虚方法、事件
public event EventHandler<MyEventArgs>? MyEvent;
protected virtual void OnMyEvent(MyEventArgs e)
{
MyEvent?.Invoke(this, e);
}
public void DoSomething()
{
OnMyEvent(new MyEventArgs("完成"));
}
}
// 3. 使用
MyPublisher publisher = new MyPublisher();
publisher.MyEvent += (s, e) => Console.WriteLine(e.Message);
publisher.DoSomething();
小结
- 委托:类型安全的方法引用
- Lambda:简化的匿名函数语法
- 多播委托:一个委托可以指向多个方法
- 事件:基于委托的发布-订阅模式
- 内置委托:Action、Func、Predicate
- 闭包:Lambda 可以捕获外部变量
练习
- 创建一个计算器类,使用委托支持不同的运算方式
- 实现一个简单的事件系统,观察股票价格变化
- 使用 Func 实现筛选和转换功能
- 实现一个进度报告系统,发布进度更新事件