跳到主要内容

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();

小结

  1. 委托:类型安全的方法引用
  2. Lambda:简化的匿名函数语法
  3. 多播委托:一个委托可以指向多个方法
  4. 事件:基于委托的发布-订阅模式
  5. 内置委托:Action、Func、Predicate
  6. 闭包:Lambda 可以捕获外部变量

练习

  1. 创建一个计算器类,使用委托支持不同的运算方式
  2. 实现一个简单的事件系统,观察股票价格变化
  3. 使用 Func 实现筛选和转换功能
  4. 实现一个进度报告系统,发布进度更新事件