目录

第八章 委托与事件

8.1 委托基础

什么是委托

委托(Delegate)是一种类型,它安全地封装了方法的引用。可以把委托看作是指向方法的指针,但它比C/C++的函数指针更安全、更灵活。

定义和使用委托

// 1. 定义委托类型
public delegate void MyDelegate(string message);
public delegate int CalculatorDelegate(int a, int b);
 
// 2. 创建与委托匹配的方法
public static void ShowMessage(string msg)
{
    Console.WriteLine($"消息: {msg}");
}
 
public static void ShowUpperMessage(string msg)
{
    Console.WriteLine($"消息: {msg.ToUpper()}");
}
 
public static int Add(int a, int b) => a + b;
public static int Multiply(int a, int b) => a * b;
 
// 3. 使用委托
public class DelegateDemo
{
    public static void Main()
    {
        // 创建委托实例
        MyDelegate del = ShowMessage;
        del("Hello World");  // 调用
 
        // 委托多播(组合多个方法)
        MyDelegate multiDel = ShowMessage;
        multiDel += ShowUpperMessage;
        multiDel("Hello");  // 两个方法都会执行
 
        // 使用CalculatorDelegate
        CalculatorDelegate calc = Add;
        Console.WriteLine(calc(5, 3));  // 8
 
        calc = Multiply;
        Console.WriteLine(calc(5, 3));  // 15
    }
}

8.2 内置委托类型

Action委托

Action是无返回值的委托,支持0-16个参数。

// Action委托
Action action1 = () => Console.WriteLine("无参数");
Action<string> action2 = msg => Console.WriteLine(msg);
Action<int, int> action3 = (a, b) => Console.WriteLine(a + b);
 
action1();
action2("Hello");
action3(5, 3);

Func委托

Func是有返回值的委托,最后一个类型参数是返回值类型。

// Func委托
Func<int> func1 = () => 42;
Func<int, int, int> func2 = (a, b) => a + b;
Func<string, bool> func3 = str => str.Length > 5;
 
Console.WriteLine(func1());        // 42
Console.WriteLine(func2(5, 3));    // 8
Console.WriteLine(func3("Hello World"));  // True

Predicate委托

Predicate<T>是返回bool的特定委托。

// Predicate委托
Predicate<int> isEven = n => n % 2 == 0;
Predicate<string> isLong = str => str.Length > 10;
 
Console.WriteLine(isEven(4));  // True
Console.WriteLine(isLong("Hi"));  // False

8.3 匿名方法与Lambda表达式

匿名方法

// 传统委托
MyDelegate del1 = ShowMessage;
 
// 匿名方法(C# 2.0)
MyDelegate del2 = delegate(string msg)
{
    Console.WriteLine($"匿名方法: {msg}");
};
 
// 使用
Action<int> printSquare = delegate(int n)
{
    Console.WriteLine(n * n);
};
printSquare(5);  // 25

Lambda表达式

// 表达式Lambda
Func<int, int> square = x => x * x;
Func<int, int, int> add = (a, b) => a + b;
 
// 语句Lambda
Action<string> greet = name =>
{
    Console.WriteLine($"Hello, {name}!");
    Console.WriteLine("Welcome!");
};
 
// 多参数
Func<int, int, int, int> sum = (a, b, c) => a + b + c;
 
// 无参数
Action sayHello = () => Console.WriteLine("Hello!");
 
// 使用
Console.WriteLine(square(5));  // 25
Console.WriteLine(add(3, 4));  // 7
greet("Alice");

8.4 委托的高级特性

委托多播

public class MulticastDemo
{
    public static void Method1() => Console.WriteLine("方法1");
    public static void Method2() => Console.WriteLine("方法2");
    public static void Method3() => Console.WriteLine("方法3");
 
    public static void Main()
    {
        Action multi = Method1;
        multi += Method2;
        multi += Method3;
 
        Console.WriteLine("=== 执行所有方法 ===");
        multi();
 
        Console.WriteLine("\n=== 移除方法2 ===");
        multi -= Method2;
        multi();
 
        // 获取调用列表
        foreach (Delegate d in multi.GetInvocationList())
        {
            Console.WriteLine($"方法: {d.Method.Name}");
        }
    }
}

泛型委托

// 自定义泛型委托
public delegate TResult MyFunc<in T, out TResult>(T arg);
 
public class GenericDelegateDemo
{
    public static void Main()
    {
        // 使用自定义泛型委托
        MyFunc<int, string> intToString = n => $"数字: {n}";
        Console.WriteLine(intToString(42));
 
        // 使用内置泛型委托
        Func<string, int> stringLength = s => s.Length;
        Console.WriteLine(stringLength("Hello"));
    }
}

8.5 事件

事件基础

事件是基于委托的一种特殊机制,用于实现发布-订阅模式。

// 定义事件参数
public class OrderEventArgs : EventArgs
{
    public string OrderId { get; set; }
    public decimal Amount { get; set; }
    public DateTime OrderTime { get; set; }
}
 
// 发布者
public class OrderProcessor
{
    // 声明事件
    public event EventHandler<OrderEventArgs> OrderPlaced;
    public event EventHandler OrderCompleted;
 
    public void PlaceOrder(string orderId, decimal amount)
    {
        Console.WriteLine($"处理订单: {orderId}");
 
        // 触发事件
        OrderPlaced?.Invoke(this, new OrderEventArgs
        {
            OrderId = orderId,
            Amount = amount,
            OrderTime = DateTime.Now
        });
 
        // 处理订单...
        Thread.Sleep(1000);
 
        // 触发完成事件
        OrderCompleted?.Invoke(this, EventArgs.Empty);
    }
}
 
// 订阅者
public class EmailNotifier
{
    public void Subscribe(OrderProcessor processor)
    {
        processor.OrderPlaced += OnOrderPlaced;
    }
 
    private void OnOrderPlaced(object sender, OrderEventArgs e)
    {
        Console.WriteLine($"[邮件通知] 订单 {e.OrderId} 已下单,金额: {e.Amount:C}");
    }
}
 
public class SmsNotifier
{
    public void Subscribe(OrderProcessor processor)
    {
        processor.OrderPlaced += (sender, e) =>
        {
            Console.WriteLine($"[短信通知] 订单 {e.OrderId} 通知已发送");
        };
    }
}
 
// 使用
var processor = new OrderProcessor();
var emailNotifier = new EmailNotifier();
var smsNotifier = new SmsNotifier();
 
emailNotifier.Subscribe(processor);
smsNotifier.Subscribe(processor);
 
processor.PlaceOrder("ORD-001", 199.99m);

8.6 标准事件模式

EventHandler<T>模式

public class Stock
{
    public string Symbol { get; }
    private decimal price;
 
    public Stock(string symbol, decimal price)
    {
        Symbol = symbol;
        this.price = price;
    }
 
    public decimal Price
    {
        get => price;
        set
        {
            if (price == value) return;
 
            decimal oldPrice = price;
            price = value;
 
            // 触发价格变化事件
            OnPriceChanged(new PriceChangedEventArgs(oldPrice, price));
        }
    }
 
    // 事件声明
    public event EventHandler<PriceChangedEventArgs> PriceChanged;
 
    // 触发事件的受保护虚方法
    protected virtual void OnPriceChanged(PriceChangedEventArgs e)
    {
        PriceChanged?.Invoke(this, e);
    }
}
 
// 事件参数
public class PriceChangedEventArgs : EventArgs
{
    public decimal OldPrice { get; }
    public decimal NewPrice { get; }
    public decimal ChangePercent => (NewPrice - OldPrice) / OldPrice * 100;
 
    public PriceChangedEventArgs(decimal oldPrice, decimal newPrice)
    {
        OldPrice = oldPrice;
        NewPrice = newPrice;
    }
}
 
// 使用
var stock = new Stock("MSFT", 150.00m);
 
stock.PriceChanged += (sender, e) =>
{
    Console.WriteLine($"股票价格变化: {e.OldPrice:C} -> {e.NewPrice:C} ({e.ChangePercent:F2}%)");
};
 
stock.Price = 155.00m;
stock.Price = 160.00m;

8.7 实战:事件驱动架构

// 温度监控示例
public class TemperatureSensor
{
    private Random random = new Random();
    private double currentTemperature;
 
    public double CurrentTemperature => currentTemperature;
 
    public event EventHandler<TemperatureEventArgs> TemperatureChanged;
    public event EventHandler<TemperatureEventArgs> TemperatureTooHigh;
    public event EventHandler<TemperatureEventArgs> TemperatureTooLow;
 
    public void SimulateReading()
    {
        // 模拟温度读数(20-40度之间随机)
        double newTemp = 20 + random.NextDouble() * 20;
        UpdateTemperature(newTemp);
    }
 
    public void SetTemperature(double temperature)
    {
        UpdateTemperature(temperature);
    }
 
    private void UpdateTemperature(double newTemp)
    {
        double oldTemp = currentTemperature;
        currentTemperature = newTemp;
 
        var args = new TemperatureEventArgs(oldTemp, newTemp);
 
        // 触发温度变化事件
        TemperatureChanged?.Invoke(this, args);
 
        // 检查阈值
        if (newTemp > 35)
        {
            TemperatureTooHigh?.Invoke(this, args);
        }
        else if (newTemp < 22)
        {
            TemperatureTooLow?.Invoke(this, args);
        }
    }
}
 
public class TemperatureEventArgs : EventArgs
{
    public double OldTemperature { get; }
    public double NewTemperature { get; }
    public double Change => NewTemperature - OldTemperature;
    public DateTime Timestamp { get; }
 
    public TemperatureEventArgs(double oldTemp, double newTemp)
    {
        OldTemperature = oldTemp;
        NewTemperature = newTemp;
        Timestamp = DateTime.Now;
    }
}
 
// 不同的监听器
public class TemperatureLogger
{
    private List<string> logs = new List<string>();
 
    public void Subscribe(TemperatureSensor sensor)
    {
        sensor.TemperatureChanged += OnTemperatureChanged;
    }
 
    private void OnTemperatureChanged(object sender, TemperatureEventArgs e)
    {
        string log = $"[{e.Timestamp:HH:mm:ss}] 温度: {e.OldTemperature:F1}°C -> {e.NewTemperature:F1}°C";
        logs.Add(log);
        Console.WriteLine($"[日志] {log}");
    }
 
    public void SaveToFile(string filename)
    {
        File.WriteAllLines(filename, logs);
    }
}
 
public class TemperatureAlert
{
    public void Subscribe(TemperatureSensor sensor)
    {
        sensor.TemperatureTooHigh += (s, e) =>
        {
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine($"⚠️ 高温警告: {e.NewTemperature:F1}°C 超过安全阈值!");
            Console.ResetColor();
        };
 
        sensor.TemperatureTooLow += (s, e) =>
        {
            Console.ForegroundColor = ConsoleColor.Blue;
            Console.WriteLine($"❄️ 低温警告: {e.NewTemperature:F1}°C 低于设定阈值!");
            Console.ResetColor();
        };
    }
}
 
public class TemperatureDisplay
{
    public void Subscribe(TemperatureSensor sensor)
    {
        sensor.TemperatureChanged += (s, e) =>
        {
            Console.WriteLine($"📊 当前温度: {e.NewTemperature:F1}°C");
        };
    }
}
 
// 使用
public class TemperatureDemo
{
    public static void Main()
    {
        var sensor = new TemperatureSensor();
        var logger = new TemperatureLogger();
        var alert = new TemperatureAlert();
        var display = new TemperatureDisplay();
 
        logger.Subscribe(sensor);
        alert.Subscribe(sensor);
        display.Subscribe(sensor);
 
        Console.WriteLine("=== 模拟温度监控 ===");
        for (int i = 0; i < 10; i++)
        {
            sensor.SimulateReading();
            Thread.Sleep(500);
        }
 
        // 触发高温
        Console.WriteLine("\n=== 测试高温警报 ===");
        sensor.SetTemperature(38);
 
        // 触发低温
        Console.WriteLine("\n=== 测试低温警报 ===");
        sensor.SetTemperature(18);
    }
}

8.8 事件最佳实践

线程安全的事件触发

public class ThreadSafePublisher
{
    private EventHandler<MyEventArgs> myEvent;
    private readonly object eventLock = new object();
 
    public event EventHandler<MyEventArgs> MyEvent
    {
        add
        {
            lock (eventLock)
            {
                myEvent += value;
            }
        }
        remove
        {
            lock (eventLock)
            {
                myEvent -= value;
            }
        }
    }
 
    protected virtual void OnMyEvent(MyEventArgs e)
    {
        EventHandler<MyEventArgs> handler;
        lock (eventLock)
        {
            handler = myEvent;
        }
        handler?.Invoke(this, e);
    }
}

练习题

基础练习

1. 委托基础:创建以下委托和使用:

  1. 一个无返回值、带string参数的委托
  2. 一个返回int、带两个int参数的委托
  3. 分别使用命名方法和Lambda表达式实例化

2. 多播委托:创建一个多播委托,依次执行以下操作:

  1. 输出当前时间
  2. 输出“任务开始”
  3. 输出“任务完成”
  4. 然后移除“任务开始”,再次执行

3. 简单事件:创建Button类,包含Click事件。创建多个按钮,为每个按钮添加不同的点击处理程序。

进阶练习

4. 文件监视器:创建FileWatcher类:

  1. FileCreated事件
  2. FileDeleted事件
  3. FileChanged事件
  4. 模拟文件系统操作并触发相应事件

5. 进度报告系统:创建FileDownloader类:

  1. DownloadProgressChanged事件(报告进度百分比)
  2. DownloadCompleted事件
  3. DownloadFailed事件
  4. 模拟下载过程并触发事件

6. 事件链:创建Workflow类,实现步骤链:

  1. StepStarted事件
  2. StepCompleted事件
  3. StepFailed事件
  4. 支持步骤重试机制

挑战练习

7. 消息总线:实现一个简单的消息总线:

  1. 支持发布/订阅模式
  2. 支持消息过滤
  3. 支持同步和异步处理
  4. 消息队列和持久化

8. 反应式编程:使用委托和事件实现简单的反应式系统:

  1. Observable和Observer模式
  2. 支持操作符(Where、Select、Merge等)
  3. 支持冷/热Observable

本章小结

本章学习了C#的委托与事件:

  1. 委托的定义、实例化和调用
  2. 内置委托类型(Action、Func、Predicate)
  3. 匿名方法与Lambda表达式
  4. 委托的多播特性
  5. 事件的声明、订阅和触发
  6. 标准事件模式
  7. 事件驱动架构实战

委托和事件是实现松耦合、可扩展架构的重要工具。