Skip to content

C# 委托和事件

本章将详细介绍 C# 中的委托和事件,包括委托的定义、多播委托、匿名方法、Lambda表达式、事件处理等概念,帮助你理解函数式编程和事件驱动编程。

委托的基本概念

什么是委托

csharp
// 委托是一种类型,表示对具有特定签名的方法的引用
// 可以把委托看作是方法的"指针"或"引用"

// 声明委托类型
public delegate int MathOperation(int a, int b);
public delegate void MessageHandler(string message);
public delegate bool Predicate<T>(T item);

// 示例方法
public static class MathHelper
{
    public static int Add(int a, int b)
    {
        Console.WriteLine($"执行加法: {a} + {b}");
        return a + b;
    }
    
    public static int Subtract(int a, int b)
    {
        Console.WriteLine($"执行减法: {a} - {b}");
        return a - b;
    }
    
    public static int Multiply(int a, int b)
    {
        Console.WriteLine($"执行乘法: {a} * {b}");
        return a * b;
    }
    
    public static int Divide(int a, int b)
    {
        if (b == 0)
            throw new DivideByZeroException("除数不能为零");
        Console.WriteLine($"执行除法: {a} / {b}");
        return a / b;
    }
}

public static class MessageHelper
{
    public static void PrintMessage(string message)
    {
        Console.WriteLine($"打印: {message}");
    }
    
    public static void LogMessage(string message)
    {
        Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] 日志: {message}");
    }
    
    public static void DebugMessage(string message)
    {
        Console.WriteLine($"DEBUG: {message}");
    }
}

static void BasicDelegateDemo()
{
    Console.WriteLine("=== 基本委托演示 ===");
    
    // 创建委托实例
    MathOperation operation;
    
    // 将方法赋值给委托
    operation = MathHelper.Add;
    int result1 = operation(10, 5);  // 调用委托
    Console.WriteLine($"结果: {result1}");
    
    // 改变委托指向的方法
    operation = MathHelper.Multiply;
    int result2 = operation(10, 5);
    Console.WriteLine($"结果: {result2}");
    
    // 使用委托作为参数
    Console.WriteLine("\n使用委托作为参数:");
    ExecuteOperation(MathHelper.Add, 8, 3);
    ExecuteOperation(MathHelper.Subtract, 8, 3);
    ExecuteOperation(MathHelper.Divide, 8, 2);
    
    // 消息处理委托
    Console.WriteLine("\n消息处理委托:");
    MessageHandler handler = MessageHelper.PrintMessage;
    handler("Hello World");
    
    handler = MessageHelper.LogMessage;
    handler("系统启动");
}

static void ExecuteOperation(MathOperation op, int a, int b)
{
    try
    {
        int result = op(a, b);
        Console.WriteLine($"操作结果: {result}");
    }
    catch (Exception ex)
    {
        Console.WriteLine($"操作失败: {ex.Message}");
    }
}

多播委托

csharp
// 多播委托可以包含多个方法的引用
// 调用委托时会依次调用所有方法

public delegate void NotificationHandler(string message);

public class NotificationSystem
{
    public static void EmailNotification(string message)
    {
        Console.WriteLine($"📧 邮件通知: {message}");
    }
    
    public static void SmsNotification(string message)
    {
        Console.WriteLine($"📱 短信通知: {message}");
    }
    
    public static void PushNotification(string message)
    {
        Console.WriteLine($"🔔 推送通知: {message}");
    }
    
    public static void LogNotification(string message)
    {
        Console.WriteLine($"📝 记录日志: [{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {message}");
    }
}

// 带返回值的委托处理
public delegate int Calculator(int x, int y);

public class CalculatorMethods
{
    public static int Add(int x, int y)
    {
        int result = x + y;
        Console.WriteLine($"Add: {x} + {y} = {result}");
        return result;
    }
    
    public static int Multiply(int x, int y)
    {
        int result = x * y;
        Console.WriteLine($"Multiply: {x} * {y} = {result}");
        return result;
    }
    
    public static int Subtract(int x, int y)
    {
        int result = x - y;
        Console.WriteLine($"Subtract: {x} - {y} = {result}");
        return result;
    }
}

static void MulticastDelegateDemo()
{
    Console.WriteLine("=== 多播委托演示 ===");
    
    // 创建多播委托
    NotificationHandler notifications = null;
    
    // 添加方法到委托
    notifications += NotificationSystem.EmailNotification;
    notifications += NotificationSystem.SmsNotification;
    notifications += NotificationSystem.PushNotification;
    notifications += NotificationSystem.LogNotification;
    
    // 调用多播委托 - 所有方法都会被调用
    Console.WriteLine("发送通知:");
    notifications?.Invoke("系统维护通知");
    
    Console.WriteLine("\n移除部分通知方式:");
    notifications -= NotificationSystem.SmsNotification;
    notifications -= NotificationSystem.PushNotification;
    
    notifications?.Invoke("重要安全更新");
    
    // 带返回值的多播委托
    Console.WriteLine("\n带返回值的多播委托:");
    Calculator calc = null;
    calc += CalculatorMethods.Add;
    calc += CalculatorMethods.Multiply;
    calc += CalculatorMethods.Subtract;
    
    // 多播委托的返回值是最后一个方法的返回值
    int finalResult = calc(5, 3);
    Console.WriteLine($"最终返回值: {finalResult}");
    
    // 获取所有方法的返回值
    Console.WriteLine("\n获取所有方法的返回值:");
    if (calc != null)
    {
        foreach (Calculator method in calc.GetInvocationList())
        {
            int result = method(10, 2);
            Console.WriteLine($"单个方法返回值: {result}");
        }
    }
}

匿名方法和Lambda表达式

匿名方法

csharp
// 匿名方法允许在需要委托的地方直接定义方法体
// 语法: delegate(参数列表) { 方法体 }

public delegate bool NumberPredicate(int number);
public delegate string StringProcessor(string input);
public delegate void ActionDelegate();

static void AnonymousMethodDemo()
{
    Console.WriteLine("=== 匿名方法演示 ===");
    
    // 使用匿名方法
    NumberPredicate isEven = delegate(int n) { return n % 2 == 0; };
    NumberPredicate isPositive = delegate(int n) { return n > 0; };
    
    int[] numbers = { -5, -2, 0, 3, 4, 7, 8, 10 };
    
    Console.WriteLine("原数组: " + string.Join(", ", numbers));
    Console.WriteLine("偶数: " + string.Join(", ", FilterNumbers(numbers, isEven)));
    Console.WriteLine("正数: " + string.Join(", ", FilterNumbers(numbers, isPositive)));
    
    // 字符串处理匿名方法
    StringProcessor toUpper = delegate(string s) { return s.ToUpper(); };
    StringProcessor addPrefix = delegate(string s) { return ">>> " + s; };
    StringProcessor reverse = delegate(string s) 
    { 
        char[] chars = s.ToCharArray();
        Array.Reverse(chars);
        return new string(chars);
    };
    
    string[] words = { "hello", "world", "csharp" };
    Console.WriteLine("\n字符串处理:");
    Console.WriteLine("原始: " + string.Join(", ", words));
    Console.WriteLine("大写: " + string.Join(", ", ProcessStrings(words, toUpper)));
    Console.WriteLine("前缀: " + string.Join(", ", ProcessStrings(words, addPrefix)));
    Console.WriteLine("反转: " + string.Join(", ", ProcessStrings(words, reverse)));
    
    // 无参数匿名方法
    ActionDelegate greet = delegate() { Console.WriteLine("Hello from anonymous method!"); };
    greet();
    
    // 捕获外部变量
    int multiplier = 3;
    NumberPredicate isMultiple = delegate(int n) { return n % multiplier == 0; };
    Console.WriteLine($"\n{multiplier}的倍数: " + string.Join(", ", FilterNumbers(numbers, isMultiple)));
}

static int[] FilterNumbers(int[] numbers, NumberPredicate predicate)
{
    List<int> result = new List<int>();
    foreach (int number in numbers)
    {
        if (predicate(number))
        {
            result.Add(number);
        }
    }
    return result.ToArray();
}

static string[] ProcessStrings(string[] strings, StringProcessor processor)
{
    string[] result = new string[strings.Length];
    for (int i = 0; i < strings.Length; i++)
    {
        result[i] = processor(strings[i]);
    }
    return result;
}

Lambda表达式

csharp
// Lambda表达式是匿名方法的简化语法
// 语法: (参数) => 表达式 或 (参数) => { 语句块 }

using System.Linq;

static void LambdaExpressionDemo()
{
    Console.WriteLine("=== Lambda表达式演示 ===");
    
    int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    
    // 基本Lambda表达式
    Func<int, bool> isEven = n => n % 2 == 0;
    Func<int, bool> isOdd = n => n % 2 != 0;
    Func<int, int> square = n => n * n;
    Func<int, string> format = n => $"Number: {n}";
    
    Console.WriteLine("原数组: " + string.Join(", ", numbers));
    
    // 使用LINQ和Lambda表达式
    var evenNumbers = numbers.Where(n => n % 2 == 0);
    var oddNumbers = numbers.Where(isOdd);
    var squares = numbers.Select(square);
    var formatted = numbers.Select(format);
    
    Console.WriteLine("偶数: " + string.Join(", ", evenNumbers));
    Console.WriteLine("奇数: " + string.Join(", ", oddNumbers));
    Console.WriteLine("平方: " + string.Join(", ", squares));
    Console.WriteLine("格式化: " + string.Join(", ", formatted));
    
    // 复杂Lambda表达式
    var complexQuery = numbers
        .Where(n => n > 3)           // 过滤大于3的数
        .Select(n => n * 2)          // 乘以2
        .Where(n => n < 20)          // 过滤小于20的数
        .OrderByDescending(n => n);   // 降序排列
    
    Console.WriteLine("复杂查询结果: " + string.Join(", ", complexQuery));
    
    // 多参数Lambda表达式
    Func<int, int, int> add = (a, b) => a + b;
    Func<int, int, int> multiply = (a, b) => a * b;
    Func<string, string, string> concat = (s1, s2) => s1 + " " + s2;
    
    Console.WriteLine($"\nLambda计算:");
    Console.WriteLine($"5 + 3 = {add(5, 3)}");
    Console.WriteLine($"5 * 3 = {multiply(5, 3)}");
    Console.WriteLine($"连接字符串: {concat("Hello", "World")}");
    
    // 语句块Lambda表达式
    Action<string> complexAction = message =>
    {
        Console.WriteLine("开始处理消息...");
        Console.WriteLine($"消息内容: {message}");
        Console.WriteLine($"消息长度: {message.Length}");
        Console.WriteLine("消息处理完成");
    };
    
    complexAction("这是一条测试消息");
    
    // 捕获外部变量
    int factor = 10;
    Func<int, int> multiplyByFactor = n => n * factor;
    
    Console.WriteLine($"\n乘以因子{factor}:");
    var multiplied = numbers.Select(multiplyByFactor);
    Console.WriteLine(string.Join(", ", multiplied));
}

内置委托类型

Action、Func和Predicate

csharp
// .NET提供了内置的委托类型,避免自定义委托

using System;
using System.Collections.Generic;
using System.Linq;

static void BuiltInDelegatesDemo()
{
    Console.WriteLine("=== 内置委托类型演示 ===");
    
    // Action - 无返回值的委托
    Action simpleAction = () => Console.WriteLine("执行简单操作");
    Action<string> messageAction = msg => Console.WriteLine($"消息: {msg}");
    Action<int, int> calculationAction = (a, b) => Console.WriteLine($"{a} + {b} = {a + b}");
    
    Console.WriteLine("Action委托:");
    simpleAction();
    messageAction("Hello Action");
    calculationAction(5, 3);
    
    // Func - 有返回值的委托
    Func<int> getRandomNumber = () => new Random().Next(1, 101);
    Func<string, int> getStringLength = s => s.Length;
    Func<int, int, int> add = (a, b) => a + b;
    Func<double, double, double, double> calculateTriangleArea = (a, b, c) =>
    {
        double s = (a + b + c) / 2;
        return Math.Sqrt(s * (s - a) * (s - b) * (s - c));
    };
    
    Console.WriteLine("\nFunc委托:");
    Console.WriteLine($"随机数: {getRandomNumber()}");
    Console.WriteLine($"字符串长度: {getStringLength("Hello World")}");
    Console.WriteLine($"加法结果: {add(10, 20)}");
    Console.WriteLine($"三角形面积: {calculateTriangleArea(3, 4, 5):F2}");
    
    // Predicate - 返回bool的委托
    Predicate<int> isEven = n => n % 2 == 0;
    Predicate<string> isLongString = s => s.Length > 5;
    Predicate<DateTime> isWeekend = date => date.DayOfWeek == DayOfWeek.Saturday || date.DayOfWeek == DayOfWeek.Sunday;
    
    Console.WriteLine("\nPredicate委托:");
    Console.WriteLine($"8是偶数: {isEven(8)}");
    Console.WriteLine($"'Hello'是长字符串: {isLongString("Hello")}");
    Console.WriteLine($"今天是周末: {isWeekend(DateTime.Now)}");
    
    // 实际应用示例
    Console.WriteLine("\n实际应用示例:");
    List<Person> people = new List<Person>
    {
        new Person("Alice", 25, "Engineer"),
        new Person("Bob", 30, "Manager"),
        new Person("Charlie", 22, "Designer"),
        new Person("Diana", 28, "Engineer"),
        new Person("Eve", 35, "Manager")
    };
    
    // 使用内置委托进行数据处理
    ProcessPeople(people);
}

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
    public string Job { get; set; }
    
    public Person(string name, int age, string job)
    {
        Name = name;
        Age = age;
        Job = job;
    }
    
    public override string ToString()
    {
        return $"{Name} ({Age}岁, {Job})";
    }
}

static void ProcessPeople(List<Person> people)
{
    Console.WriteLine("所有人员:");
    people.ForEach(p => Console.WriteLine($"  {p}"));
    
    // 使用Predicate过滤
    Predicate<Person> isEngineer = p => p.Job == "Engineer";
    Predicate<Person> isYoung = p => p.Age < 30;
    
    var engineers = people.FindAll(isEngineer);
    var youngPeople = people.FindAll(isYoung);
    
    Console.WriteLine("\n工程师:");
    engineers.ForEach(p => Console.WriteLine($"  {p}"));
    
    Console.WriteLine("\n年轻人 (< 30岁):");
    youngPeople.ForEach(p => Console.WriteLine($"  {p}"));
    
    // 使用Func转换
    Func<Person, string> getNameAndAge = p => $"{p.Name} ({p.Age})";
    var nameAgeList = people.Select(getNameAndAge);
    
    Console.WriteLine("\n姓名和年龄:");
    Console.WriteLine($"  {string.Join(", ", nameAgeList)}");
    
    // 使用Action执行操作
    Action<Person> promoteEngineer = p =>
    {
        if (p.Job == "Engineer" && p.Age > 25)
        {
            Console.WriteLine($"  提升 {p.Name} 为高级工程师");
        }
    };
    
    Console.WriteLine("\n晋升操作:");
    people.ForEach(promoteEngineer);
}

事件

事件的基本概念

csharp
// 事件是一种特殊的多播委托,提供了发布-订阅模式的实现
// 事件只能在声明它的类内部触发,外部只能订阅和取消订阅

// 定义事件参数类
public class TemperatureChangedEventArgs : EventArgs
{
    public double OldTemperature { get; }
    public double NewTemperature { get; }
    public DateTime Timestamp { get; }
    
    public TemperatureChangedEventArgs(double oldTemp, double newTemp)
    {
        OldTemperature = oldTemp;
        NewTemperature = newTemp;
        Timestamp = DateTime.Now;
    }
}

// 温度传感器类
public class TemperatureSensor
{
    private double temperature;
    private string sensorId;
    
    // 声明事件
    public event EventHandler<TemperatureChangedEventArgs> TemperatureChanged;
    
    public TemperatureSensor(string sensorId, double initialTemperature = 20.0)
    {
        this.sensorId = sensorId;
        this.temperature = initialTemperature;
    }
    
    public string SensorId => sensorId;
    
    public double Temperature
    {
        get => temperature;
        set
        {
            if (Math.Abs(temperature - value) > 0.1) // 温度变化超过0.1度才触发事件
            {
                double oldTemp = temperature;
                temperature = value;
                OnTemperatureChanged(new TemperatureChangedEventArgs(oldTemp, temperature));
            }
        }
    }
    
    // 触发事件的受保护方法
    protected virtual void OnTemperatureChanged(TemperatureChangedEventArgs e)
    {
        TemperatureChanged?.Invoke(this, e);
    }
    
    // 模拟温度变化
    public void SimulateTemperatureChange()
    {
        Random random = new Random();
        double change = (random.NextDouble() - 0.5) * 10; // -5到+5度的随机变化
        Temperature += change;
    }
}

// 温度监控系统
public class TemperatureMonitor
{
    private string monitorName;
    
    public TemperatureMonitor(string name)
    {
        monitorName = name;
    }
    
    public void Subscribe(TemperatureSensor sensor)
    {
        sensor.TemperatureChanged += OnTemperatureChanged;
        Console.WriteLine($"{monitorName} 开始监控传感器 {sensor.SensorId}");
    }
    
    public void Unsubscribe(TemperatureSensor sensor)
    {
        sensor.TemperatureChanged -= OnTemperatureChanged;
        Console.WriteLine($"{monitorName} 停止监控传感器 {sensor.SensorId}");
    }
    
    private void OnTemperatureChanged(object sender, TemperatureChangedEventArgs e)
    {
        TemperatureSensor sensor = sender as TemperatureSensor;
        Console.WriteLine($"[{monitorName}] 传感器 {sensor.SensorId}: " +
                         $"{e.OldTemperature:F1}°C -> {e.NewTemperature:F1}°C " +
                         $"(变化: {e.NewTemperature - e.OldTemperature:+0.0;-0.0}°C) " +
                         $"时间: {e.Timestamp:HH:mm:ss}");
    }
}

// 温度报警系统
public class TemperatureAlarm
{
    private double highThreshold;
    private double lowThreshold;
    
    public TemperatureAlarm(double lowThreshold = 0, double highThreshold = 40)
    {
        this.lowThreshold = lowThreshold;
        this.highThreshold = highThreshold;
    }
    
    public void Subscribe(TemperatureSensor sensor)
    {
        sensor.TemperatureChanged += CheckTemperatureAlarm;
    }
    
    public void Unsubscribe(TemperatureSensor sensor)
    {
        sensor.TemperatureChanged -= CheckTemperatureAlarm;
    }
    
    private void CheckTemperatureAlarm(object sender, TemperatureChangedEventArgs e)
    {
        TemperatureSensor sensor = sender as TemperatureSensor;
        
        if (e.NewTemperature > highThreshold)
        {
            Console.WriteLine($"🚨 高温报警! 传感器 {sensor.SensorId}: {e.NewTemperature:F1}°C (阈值: {highThreshold}°C)");
        }
        else if (e.NewTemperature < lowThreshold)
        {
            Console.WriteLine($"🧊 低温报警! 传感器 {sensor.SensorId}: {e.NewTemperature:F1}°C (阈值: {lowThreshold}°C)");
        }
    }
}

static void BasicEventDemo()
{
    Console.WriteLine("=== 基本事件演示 ===");
    
    // 创建温度传感器
    TemperatureSensor sensor1 = new TemperatureSensor("客厅传感器", 22.0);
    TemperatureSensor sensor2 = new TemperatureSensor("卧室传感器", 20.0);
    
    // 创建监控和报警系统
    TemperatureMonitor monitor = new TemperatureMonitor("中央监控");
    TemperatureAlarm alarm = new TemperatureAlarm(5, 35);
    
    // 订阅事件
    monitor.Subscribe(sensor1);
    monitor.Subscribe(sensor2);
    alarm.Subscribe(sensor1);
    alarm.Subscribe(sensor2);
    
    // 模拟温度变化
    Console.WriteLine("\n开始模拟温度变化:");
    for (int i = 0; i < 8; i++)
    {
        Console.WriteLine($"\n--- 第 {i + 1} 次变化 ---");
        sensor1.SimulateTemperatureChange();
        sensor2.SimulateTemperatureChange();
        Thread.Sleep(500); // 暂停500毫秒
    }
    
    // 手动设置极端温度
    Console.WriteLine("\n--- 手动设置极端温度 ---");
    sensor1.Temperature = 45.0;  // 触发高温报警
    sensor2.Temperature = -5.0;  // 触发低温报警
    
    // 取消订阅
    Console.WriteLine("\n--- 取消监控 ---");
    monitor.Unsubscribe(sensor1);
    alarm.Unsubscribe(sensor2);
    
    // 再次变化,只有部分系统会响应
    Console.WriteLine("\n--- 部分取消订阅后的变化 ---");
    sensor1.Temperature = 25.0;
    sensor2.Temperature = 18.0;
}

自定义事件

csharp
// 更复杂的事件示例:文件下载器

public enum DownloadStatus
{
    Started,
    InProgress,
    Completed,
    Failed,
    Cancelled
}

public class DownloadProgressEventArgs : EventArgs
{
    public string FileName { get; }
    public long BytesReceived { get; }
    public long TotalBytes { get; }
    public double ProgressPercentage { get; }
    public TimeSpan ElapsedTime { get; }
    
    public DownloadProgressEventArgs(string fileName, long bytesReceived, long totalBytes, TimeSpan elapsedTime)
    {
        FileName = fileName;
        BytesReceived = bytesReceived;
        TotalBytes = totalBytes;
        ProgressPercentage = totalBytes > 0 ? (double)bytesReceived / totalBytes * 100 : 0;
        ElapsedTime = elapsedTime;
    }
}

public class DownloadStatusEventArgs : EventArgs
{
    public string FileName { get; }
    public DownloadStatus Status { get; }
    public string Message { get; }
    public Exception Exception { get; }
    
    public DownloadStatusEventArgs(string fileName, DownloadStatus status, string message = null, Exception exception = null)
    {
        FileName = fileName;
        Status = status;
        Message = message;
        Exception = exception;
    }
}

public class FileDownloader
{
    // 定义事件
    public event EventHandler<DownloadProgressEventArgs> ProgressChanged;
    public event EventHandler<DownloadStatusEventArgs> StatusChanged;
    
    private bool isCancelled;
    
    public async Task DownloadFileAsync(string fileName, long fileSize)
    {
        isCancelled = false;
        DateTime startTime = DateTime.Now;
        
        try
        {
            // 触发开始事件
            OnStatusChanged(new DownloadStatusEventArgs(fileName, DownloadStatus.Started, "开始下载"));
            
            long bytesDownloaded = 0;
            long chunkSize = Math.Max(fileSize / 20, 1024); // 分20次下载,每次至少1KB
            
            while (bytesDownloaded < fileSize && !isCancelled)
            {
                // 模拟下载延迟
                await Task.Delay(200);
                
                // 模拟下载数据块
                long remainingBytes = fileSize - bytesDownloaded;
                long currentChunk = Math.Min(chunkSize, remainingBytes);
                bytesDownloaded += currentChunk;
                
                // 触发进度事件
                TimeSpan elapsed = DateTime.Now - startTime;
                OnProgressChanged(new DownloadProgressEventArgs(fileName, bytesDownloaded, fileSize, elapsed));
                
                // 模拟随机失败
                if (new Random().Next(1, 100) <= 2) // 2%的失败概率
                {
                    throw new Exception("网络连接中断");
                }
            }
            
            if (isCancelled)
            {
                OnStatusChanged(new DownloadStatusEventArgs(fileName, DownloadStatus.Cancelled, "下载已取消"));
            }
            else
            {
                OnStatusChanged(new DownloadStatusEventArgs(fileName, DownloadStatus.Completed, "下载完成"));
            }
        }
        catch (Exception ex)
        {
            OnStatusChanged(new DownloadStatusEventArgs(fileName, DownloadStatus.Failed, "下载失败", ex));
        }
    }
    
    public void CancelDownload()
    {
        isCancelled = true;
    }
    
    protected virtual void OnProgressChanged(DownloadProgressEventArgs e)
    {
        ProgressChanged?.Invoke(this, e);
    }
    
    protected virtual void OnStatusChanged(DownloadStatusEventArgs e)
    {
        StatusChanged?.Invoke(this, e);
    }
}

// 下载管理器
public class DownloadManager
{
    private Dictionary<string, FileDownloader> activeDownloads;
    
    public DownloadManager()
    {
        activeDownloads = new Dictionary<string, FileDownloader>();
    }
    
    public async Task StartDownload(string fileName, long fileSize)
    {
        if (activeDownloads.ContainsKey(fileName))
        {
            Console.WriteLine($"文件 {fileName} 已在下载中");
            return;
        }
        
        FileDownloader downloader = new FileDownloader();
        
        // 订阅事件
        downloader.ProgressChanged += OnDownloadProgress;
        downloader.StatusChanged += OnDownloadStatusChanged;
        
        activeDownloads[fileName] = downloader;
        
        await downloader.DownloadFileAsync(fileName, fileSize);
        
        // 下载完成后清理
        activeDownloads.Remove(fileName);
    }
    
    public void CancelDownload(string fileName)
    {
        if (activeDownloads.TryGetValue(fileName, out FileDownloader downloader))
        {
            downloader.CancelDownload();
        }
    }
    
    private void OnDownloadProgress(object sender, DownloadProgressEventArgs e)
    {
        Console.WriteLine($"📥 {e.FileName}: {e.ProgressPercentage:F1}% " +
                         $"({e.BytesReceived:N0}/{e.TotalBytes:N0} 字节) " +
                         $"用时: {e.ElapsedTime:mm\\:ss}");
    }
    
    private void OnDownloadStatusChanged(object sender, DownloadStatusEventArgs e)
    {
        string statusIcon = e.Status switch
        {
            DownloadStatus.Started => "🚀",
            DownloadStatus.Completed => "✅",
            DownloadStatus.Failed => "❌",
            DownloadStatus.Cancelled => "⏹️",
            _ => "ℹ️"
        };
        
        Console.WriteLine($"{statusIcon} {e.FileName}: {e.Message}");
        
        if (e.Exception != null)
        {
            Console.WriteLine($"   错误详情: {e.Exception.Message}");
        }
    }
}

static async Task CustomEventDemo()
{
    Console.WriteLine("=== 自定义事件演示 ===");
    
    DownloadManager manager = new DownloadManager();
    
    // 启动多个下载任务
    var downloadTasks = new List<Task>
    {
        manager.StartDownload("document.pdf", 1024 * 1024 * 5),    // 5MB
        manager.StartDownload("video.mp4", 1024 * 1024 * 50),      // 50MB
        manager.StartDownload("music.mp3", 1024 * 1024 * 8),       // 8MB
    };
    
    // 等待2秒后取消一个下载
    _ = Task.Run(async () =>
    {
        await Task.Delay(2000);
        Console.WriteLine("\n🛑 取消 video.mp4 下载");
        manager.CancelDownload("video.mp4");
    });
    
    // 等待所有下载完成
    await Task.WhenAll(downloadTasks);
    
    Console.WriteLine("\n所有下载任务处理完成");
}

实践示例

示例:观察者模式实现

csharp
// 使用事件实现观察者模式

public interface IObserver<T>
{
    void Update(T data);
}

public class Observable<T>
{
    public event Action<T> DataChanged;
    
    protected virtual void OnDataChanged(T data)
    {
        DataChanged?.Invoke(data);
    }
    
    public void Subscribe(Action<T> observer)
    {
        DataChanged += observer;
    }
    
    public void Unsubscribe(Action<T> observer)
    {
        DataChanged -= observer;
    }
}

// 股票价格监控系统
public class Stock : Observable<StockPrice>
{
    private string symbol;
    private decimal currentPrice;
    
    public Stock(string symbol, decimal initialPrice)
    {
        this.symbol = symbol;
        this.currentPrice = initialPrice;
    }
    
    public string Symbol => symbol;
    public decimal CurrentPrice => currentPrice;
    
    public void UpdatePrice(decimal newPrice)
    {
        if (newPrice != currentPrice)
        {
            decimal oldPrice = currentPrice;
            currentPrice = newPrice;
            
            OnDataChanged(new StockPrice
            {
                Symbol = symbol,
                OldPrice = oldPrice,
                NewPrice = newPrice,
                Change = newPrice - oldPrice,
                ChangePercentage = oldPrice != 0 ? (newPrice - oldPrice) / oldPrice * 100 : 0,
                Timestamp = DateTime.Now
            });
        }
    }
}

public class StockPrice
{
    public string Symbol { get; set; }
    public decimal OldPrice { get; set; }
    public decimal NewPrice { get; set; }
    public decimal Change { get; set; }
    public decimal ChangePercentage { get; set; }
    public DateTime Timestamp { get; set; }
}

// 股票观察者
public class StockDisplay : IObserver<StockPrice>
{
    private string displayName;
    
    public StockDisplay(string name)
    {
        displayName = name;
    }
    
    public void Update(StockPrice data)
    {
        string changeIcon = data.Change >= 0 ? "📈" : "📉";
        string changeColor = data.Change >= 0 ? "+" : "";
        
        Console.WriteLine($"[{displayName}] {changeIcon} {data.Symbol}: " +
                         $"${data.NewPrice:F2} " +
                         $"({changeColor}{data.Change:F2}, {data.ChangePercentage:+0.00;-0.00}%) " +
                         $"@ {data.Timestamp:HH:mm:ss}");
    }
}

public class StockAlert : IObserver<StockPrice>
{
    private decimal alertThreshold;
    
    public StockAlert(decimal threshold)
    {
        alertThreshold = threshold;
    }
    
    public void Update(StockPrice data)
    {
        if (Math.Abs(data.ChangePercentage) >= alertThreshold)
        {
            string alertType = data.ChangePercentage > 0 ? "涨幅" : "跌幅";
            Console.WriteLine($"🚨 价格警报! {data.Symbol} {alertType}超过 {alertThreshold}%: " +
                             $"{data.ChangePercentage:+0.00;-0.00}%");
        }
    }
}

public class StockLogger : IObserver<StockPrice>
{
    private List<StockPrice> priceHistory;
    
    public StockLogger()
    {
        priceHistory = new List<StockPrice>();
    }
    
    public void Update(StockPrice data)
    {
        priceHistory.Add(data);
        Console.WriteLine($"📝 记录 {data.Symbol} 价格变化到日志");
    }
    
    public void ShowHistory(string symbol, int count = 5)
    {
        var history = priceHistory
            .Where(p => p.Symbol == symbol)
            .TakeLast(count)
            .ToList();
        
        Console.WriteLine($"\n{symbol} 最近 {history.Count} 次价格变化:");
        foreach (var price in history)
        {
            Console.WriteLine($"  {price.Timestamp:HH:mm:ss}: ${price.NewPrice:F2} " +
                             $"({price.ChangePercentage:+0.00;-0.00}%)");
        }
    }
}

static async Task ObserverPatternDemo()
{
    Console.WriteLine("=== 观察者模式演示 ===");
    
    // 创建股票
    Stock appleStock = new Stock("AAPL", 150.00m);
    Stock googleStock = new Stock("GOOGL", 2800.00m);
    
    // 创建观察者
    StockDisplay mainDisplay = new StockDisplay("主显示器");
    StockDisplay mobileDisplay = new StockDisplay("手机应用");
    StockAlert priceAlert = new StockAlert(2.0m); // 2%变化警报
    StockLogger logger = new StockLogger();
    
    // 订阅事件
    appleStock.Subscribe(mainDisplay.Update);
    appleStock.Subscribe(mobileDisplay.Update);
    appleStock.Subscribe(priceAlert.Update);
    appleStock.Subscribe(logger.Update);
    
    googleStock.Subscribe(mainDisplay.Update);
    googleStock.Subscribe(priceAlert.Update);
    googleStock.Subscribe(logger.Update);
    
    // 模拟价格变化
    Console.WriteLine("开始股票价格模拟...\n");
    
    Random random = new Random();
    for (int i = 0; i < 10; i++)
    {
        // Apple 股票价格变化
        decimal appleChange = (decimal)(random.NextDouble() - 0.5) * 10; // -5 到 +5
        appleStock.UpdatePrice(appleStock.CurrentPrice + appleChange);
        
        // Google 股票价格变化
        decimal googleChange = (decimal)(random.NextDouble() - 0.5) * 100; // -50 到 +50
        googleStock.UpdatePrice(googleStock.CurrentPrice + googleChange);
        
        Console.WriteLine();
        await Task.Delay(1000); // 等待1秒
    }
    
    // 显示历史记录
    logger.ShowHistory("AAPL");
    logger.ShowHistory("GOOGL");
}

本章小结

本章详细介绍了 C# 中的委托和事件:

关键要点:

  • 委托:类型安全的函数指针,可以引用静态方法或实例方法
  • 多播委托:可以包含多个方法引用的委托
  • 匿名方法:使用 delegate 关键字定义的内联方法
  • Lambda表达式:匿名方法的简化语法
  • 内置委托:Action、Func、Predicate等预定义委托类型
  • 事件:基于委托的发布-订阅模式实现

委托的优势:

  • 类型安全:编译时检查方法签名
  • 灵活性:运行时动态绑定方法
  • 解耦:降低组件间的直接依赖
  • 回调支持:实现回调机制

事件的特点:

  • 封装性:只能在声明类内部触发
  • 安全性:外部只能订阅/取消订阅
  • 多播:支持多个订阅者
  • 标准模式:遵循 .NET 事件模式

最佳实践:

  • 使用内置委托类型而不是自定义委托
  • 事件参数继承自 EventArgs
  • 使用 null 条件运算符安全调用委托
  • 及时取消事件订阅避免内存泄漏
  • 在触发事件前检查是否为 null

应用场景:

  • 事件驱动编程
  • 观察者模式实现
  • 回调机制
  • 异步编程
  • LINQ 查询

下一步: 在下一章中,我们将学习异常处理,了解如何优雅地处理程序中的错误和异常情况。

延伸阅读

本站内容仅供学习和研究使用。