委托
委托就像是方法的类型,有了委托就可以把方法当参数传来传去,或者在一个变量里随时换掉要执行的方法。
// 1. 声明一个委托类型(定义:我能代表哪种方法) public delegate void MyDelegate(string msg); // 2. 写一个符合签名的方法 public static void ShowMessage(string text) { Console.WriteLine($"消息:{text}"); } // 3. 使用 MyDelegate del = ShowMessage; // 把方法赋值给委托变量 del("Hello"); // 调用委托,相当于调用 ShowMessage大多数情况下不需要自己声明委托,直接用系统自带的就行。
// Action 示例(无返回值) Action<string> print = s => Console.WriteLine(s); print("直接用 Action"); // Func 示例(有返回值) Func<int, int, int> add = (a, b) => a + b; int result = add(3, 4); // 7委托可以串联多个方法,调用一次就会按顺序执行全部,也就是多播。
Action act = MethodA; act += MethodB; act += MethodC; act(); // 依次执行 A、B、C // 移除方法 act -= MethodB;事件
事件就是加了限制的委托,比如只能从外部“订阅”和“取消订阅”,不能直接触发或覆盖。相当于给委托封装了一下。
下面举个例子:
// 定义一个事件发布者 public class Button { // 事件:外部可以订阅,但不能随意触发 public event EventHandler? Clicked; // 模拟点击按钮 public void Click() { Console.WriteLine("按钮被点击了"); // 触发事件(只能在类内部触发) Clicked?.Invoke(this, EventArgs.Empty); } } // 使用 class Program { static void Main() { Button btn = new Button(); // 订阅事件(用 +=) btn.Clicked += OnButtonClicked; // 触发点击 btn.Click(); // 取消订阅(用 -=) btn.Clicked -= OnButtonClicked; // btn.Clicked.Invoke(null, null); ❌ 编译错误,外部不能触发 // btn.Clicked = null; ❌ 编译错误,外部不能赋值 } static void OnButtonClicked(object? sender, EventArgs e) { Console.WriteLine("事件处理:按钮被点击了!"); } }输出是:
按钮被点击了 事件处理:按钮被点击了!在.NET中推荐是使用EventHandler和EventHandler<T>,它有两个参数:
public event EventHandler<CustomEventArgs>? DataReceived;标准写法是:
public class DataProcessor { // 声明事件(使用标准 EventHandler<T>) public event EventHandler<ProcessCompletedEventArgs>? ProcessCompleted; public void StartProcess() { // 模拟处理 Console.WriteLine("处理中..."); Thread.Sleep(1000); // 处理完成,触发事件 OnProcessCompleted(10); // 假设处理了10条数据 } // 受保护的触发方法(约定:On + 事件名) protected virtual void OnProcessCompleted(int count) { ProcessCompleted?.Invoke(this, new ProcessCompletedEventArgs(count)); } } // 自定义事件参数类 public class ProcessCompletedEventArgs : EventArgs { public int RecordCount { get; } public ProcessCompletedEventArgs(int count) => RecordCount = count; } // 使用 class Program { static void Main() { var processor = new DataProcessor(); processor.ProcessCompleted += (sender, e) => { Console.WriteLine($"处理完成!共 {e.RecordCount} 条数据"); }; processor.StartProcess(); } }Lambda表达式
lambda表达式就是匿名方法的简写,是C#中最常用最实用的特性之一。
传统写法:
// 定义方法 bool IsEven(int x) { return x % 2 == 0; } // 使用 List<int> numbers = new List<int>{1,2,3,4,5}; var evens = numbers.Where(IsEven);lambda写法:
var evens = numbers.Where(x => x % 2 == 0);最直观的看到lambda写法少写了三行代码,显著减少了代码冗余。
lambda可以捕获外部变量,也就是闭包:
int multiplier = 3; Func<int, int> multiply = x => x * multiplier; // 捕获了 multiplier int result = multiply(5); // 15 multiplier = 10; result = multiply(5); // 50(外部变量变化,Lambda 也跟着变)但在循环中捕获变量时,很容易造成“延迟执行”的问题。
var actions = new List<Action>(); for (int i = 0; i < 3; i++) { actions.Add(() => Console.WriteLine(i)); // 捕获同一个 i } foreach (var action in actions) { action(); // 输出 3, 3, 3 而不是 0,1,2 }解决方法也很简单,就是在循环内局部复制一份:
for (int i = 0; i < 3; i++) { int copy = i; // 每次循环新建一个变量 actions.Add(() => Console.WriteLine(copy)); } // 输出 0,1,2一般情况下,编译器会根据上下文推断lambda的类型,只有上下文不够明确时,才需要显示声明类型:
// ❌ 错误:无法推断 var lambda = x => x * 2; // ✅ 正确:明确委托类型 Func<int, int> lambda = x => x * 2;