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 查询
下一步: 在下一章中,我们将学习异常处理,了解如何优雅地处理程序中的错误和异常情况。