C#高级编程9-第8章 委托、lamdba表达式和事件
委托、lamdba表达式和事件
1.引用方法
函数指针是一个指向内存位置的指针,不是类型安全的。无法判断实际指向。参数和返回类型也无从知晓。
.NET委托是类型安全的。定义了返回类型和参数类型,不仅包含方法引用,还可以包含多个方法引用。
2.委托
使用方法作为参数进行传递,必须把方法细节进行封装到一个新类型的对象中,即委托。
委托是一种特殊类型的对象。我们之前定义的对象都包含数据。而委托包含的是多个方法的地址。
声明委托
委托使用delegate声明。通过指定返回类型、签名以及参数类型进行创建。
创建委托的一个或多个实例,编译器将在后台创建表示该委托的一个类。
delegate void InitMethodInvoker(int x);
该委托方法无返回值,参数类型是int,每个实例都会有这个方法的引用。该委托类似于方法的定义,没有方法体。
委托可以使用访问修饰符进行修饰:
private delegate void InitMethodInvoker(int x);
定义委托后,创建它的实例,从而实现它的细节即方法体。
使用委托
class MathOperations { public static double MultiplayByTwo(double value) { return value * 2; } public static double Square(double value) { return value * value; } }
//这个类中定义了委托的实例
public delegate double DoubleOp(double x);//声明委托 static void Main(string[] args) { DoubleOp[] operations = { MathOperations.MultiplayByTwo,//指定委托实例方法 MathOperations.Square }; for (int i = 0; i < operations.Length; i++) {
//将委托实例方法作为参数传递 ProcessAndDisplayNumber(operations[i], 2.0);//operations[i]是委托即方法参数 ProcessAndDisplayNumber(operations[i], 15); } } static void ProcessAndDisplayNumber(DoubleOp action,double value) { double result = action( value);//实现委托 Console.WriteLine("参数值value:" + value + ",结果值result:" + result); }
通常情况下需要做安全措施。如果这个方法使用DoubleOp委托实例方法作为参数传递,如果传入null值,到了action( value)会出现异常。因此需要在方法里面加上判断
ProcessAndDisplayNumber(null, 15);
static void ProcessAndDisplayNumber(DoubleOp action,double value) { if(action!=null) double result = action( value);//实现委托 Console.WriteLine("参数值value:" + value + ",结果值result:" + result); }
系统委托
系统委托有4中:Action类、Func类、Predicate<T>、Comparison<T>委托
Action类的委托
- Action委托 封装一个方法,该方法不具有参数并且不返回值
-
Action<T>委托 封装一个方法,该方法只有一个参数并且不返回值
- Action<T1,T2>委托 封装一个方法,该方法具有两个参数并且不返回值
-
static void Main(string[] args) { #region Action<T>委托示例 //需求:打印出整型集合list的元素 List<int> list = new List<int>() { 1, 2, 3, 4, 5 }; //将匿名方法分配给 Action<T> 委托实例 Action<int> concat1 = delegate(int i) { Console.WriteLine(i); }; list.ForEach(concat1); //将 lambda 表达式分配给 Action<T> 委托实例 Action<int> concat2 = (i => Console.WriteLine(i)); list.ForEach(concat2); Console.ReadKey(); #endregion }
Func类的委托
- 1.Func(TResult)委托封装封装一个不具有参数但却返回 TResult 参数指定的类型值的方法
- Func(T,TResult)委托 封装一个具有一个参数并返回 TResult 参数指定的类型值的方法
- Func(T1,T2,TResult)委托 封装一个具有两个参数并返回 TResult 参数指定的类型值的方法
-
static void Main(string[] args) { #region Func<T,TResult>委托示例 //需求:查找整型集合list中大于3的所有元素组成的新集合,并打印出集合元素 List<int> list = new List<int>() { 1, 2, 3, 4, 5 }; //将匿名方法分配给 Func<T,TResult> 委托实例 Func<int, bool> concat1 = delegate(int i) { return i > 3; }; var newlist1 = list.Where(concat1).ToList(); //将 Lambda 表达式分配给 Func<T,TResult> 委托实例 Func<int, bool> concat2 = i => i > 3; var newlist2 = list.Where(concat2).ToList(); newlist1.ForEach(i => Console.WriteLine(i.ToString())); newlist2.ForEach(i => Console.WriteLine(i.ToString())); Console.ReadKey(); #endregion }
Predicate<T>委托
表示定义一组条件并确定指定对象是否符合这些条件的方法
-
static void Main(string[] args) { #region Predicate<T>委托示例 //需求:查找整型集合list中大于3的所有元素组成的新集合,并打印出集合元素 List<int> list = new List<int>() { 1, 2, 3, 4, 5 }; //将匿名方法分配给 Predicate<T> 委托实例 Predicate<int> concat1 = delegate(int i) { return i > 3; }; var newlist1 = list.FindAll(concat1); //将 lambda 表达式分配给 Predicate<T> 委托实例 Predicate<int> concat2 = (c => c > 3); var newlist2 = list.FindAll(concat2); newlist1.ForEach(i => Console.WriteLine(i)); newlist2.ForEach(i => Console.WriteLine(i)); Console.ReadKey(); #endregion }
Comparison<T>委托
表示比较同一类型的两个对象的方法
-
static void Main(string[] args) { #region Comparison<T>委托示例 //需求:将整型集合list中的所有元素倒序排列打印出来 List<int> list = new List<int>() { 1, 2, 3, 4, 5 }; //将匿名方法分配给 Comparison<T> 委托实例 Comparison<int> concat1 = delegate(int i, int j) { return j - i; }; //将 lambda 表达式分配给 Comparison<T> 委托实例 Comparison<int> concat2 = (i, j) => j - i; list.Sort(concat1); list.ForEach(c => Console.WriteLine(c.ToString())); list.Sort(concat2); list.ForEach(c => Console.WriteLine(c.ToString())); Console.ReadKey(); #endregion }
BubbleSorter
说明了委托真正的意图,首先定义一个Employee类,类中定义静态方法CompareSalary
public static bool CompareSalary(Employee e1, Employee e2) { return e1.Salary < e2.Salary; }
然后我们再定义一个类BubbleSorter,类中定义静态方法Sort
static public void Sort<T>(IList<T> sortArray, Func<T, T, bool> comparison) { bool swapped = true; do { swapped = false; for (int i = 0; i < sortArray.Count - 1; i++) { if (comparison(sortArray[i + 1], sortArray[i])) { T temp = sortArray[i]; sortArray[i] = sortArray[i + 1]; sortArray[i + 1] = temp; swapped = true; } } } while (swapped); }
接下来我们分析一下:
Func<T, T, bool>是系统定义的委托,该委托具有2个参数,一个返回值,委托参数类型T根据调用Sort方法时进行指定(BubbleSorter.Sort<Employee>(..,..,))。然后我们看看所有执行代码:
static void Main() { Employee[] employees = { new Employee("Bugs Bunny", 20000), new Employee("Elmer Fudd", 10000), new Employee("Daffy Duck", 25000), new Employee("Wile Coyote", 1000000.38m), new Employee("Foghorn Leghorn", 23000), new Employee("RoadRunner", 50000) };
//将Employee.CompareSalary方法作为参数进行传递,记住Employee.CompareSalary是一个委托实例类型,它目前不属于Func<T, T, bool>的实例,但是
//它符合Func<T, T, bool>类型,因此可以作为Func<T, T, bool>的实例进行参数传递。
BubbleSorter.Sort(employees, Employee.CompareSalary);
foreach (var employee in employees)
{
Console.WriteLine(employee);
}
}
多播委托
委托可以包含多个方法,可以多次显式调用这个委托。
需要注意的是,多播委托需要连续的调用多个方法,并且委托的返回结构是void,否则就只能得到最后一个方法的结果。
可以使用“+=”或者“-=”添加和删除方法。

class MathOperations { public static void MultiplyByTwo(double value) { double result = value * 2; Console.WriteLine("Multiplying by 2: {0} gives {1}", value, result); } public static void Square(double value) { double result = value * value; Console.WriteLine("Squaring: {0} gives {1}", value, result); } }

static void Main() { Action<double> operations = MathOperations.MultiplyByTwo;// value*2 operations += MathOperations.Square;//value*value ProcessAndDisplayNumber(operations, 2.0); ProcessAndDisplayNumber(operations, 7.94); ProcessAndDisplayNumber(operations, 1.414); Console.WriteLine(); } static void ProcessAndDisplayNumber(Action<double> action, double value) { Console.WriteLine(); Console.WriteLine("ProcessAndDisplayNumber called with value = {0}", value); action(value); }
ProcessAndDisplayNumber called with value = 2 Multiplying by 2: 2 gives 4 Squaring: 2 gives 4 ProcessAndDisplayNumber called with value = 7.94 Multiplying by 2: 7.94 gives 15.88 Squaring: 7.94 gives 63.0436 ProcessAndDisplayNumber called with value = 1.414 Multiplying by 2: 1.414 gives 2.828 Squaring: 1.414 gives 1.999396
虽然可以执行多个方法,但是如果执行到其中一个方法时失败了,那么后面的方法得不到执行。此时需要一个解决办法。
.NET中的系统委托定义了一个方法:GetInvocationList()通过这个方法能够得到所有需要执行的委托方法。然后我们迭代一下就可以处理其中一个方法失败,其他方法继续运行。
Action d1 = One; d1 += Two; Delegate[] delegates = d1.GetInvocationList(); foreach (Action d in delegates) { try { d(); } catch (Exception) { Console.WriteLine("Exception caught"); } }

static void One() { Console.WriteLine("One"); throw new Exception("Error in one"); } static void Two() { Console.WriteLine("Two"); }
匿名方法
原来使用委托,我们需要定义委托的委托的方法实例。现在我们需要简化它的操作,直接使用delegate关键字声明并定义。
在后面使用lamdba表达式之后会进行再次简化工作。使委托得到了更加灵活广泛的使用。
static void Main() { string mid = ", middle part,"; Func<string, string> anonDel = delegate(string param) { param += mid; param += " and this was added to the string."; return param; }; Console.WriteLine(anonDel("Start of string")); }
3.lamdba表达式
只要有委托的地方均可以使用lamdba表达式。声明委托的方法将变得更简单。
使用param =>代替了delegate(string param)的声明
string mid = ",middle part,"; Func<string, string> lambda = param => { param += mid; param += "and this was added to the string"; return param; }; Console.WriteLine(lambda("start of string"));
结果:
start of string,middle part,and this was added to the string
参数
lamdba表达式多个参数进行定义声明
下面的委托(x, y)=>x*y是委托Func<double, double, double>的实例,
如果委托只有一个参数的话直接使用x=>y;就可以了;代表实现了Func<double,double>
如果委托有一个以上参数的话需要用括号包起来;=>指定了返回值。
如果需要在定义时指定参数的类型,也可以将参数的数据类型加上。
Func<double, double, double> twoParams = (x, y) => x * y; Console.WriteLine(twoParams(3, 2)); Func<double, double, double> twoParamsWithTypes = (double x, double y) => x * y; Console.WriteLine(twoParamsWithTypes(4, 2));
多行代码
对于简单的(x, y) => x * y的表达式,编译器会给一条隐式的return返回语句。如果这个委托里面需要做的事情不仅仅是x*y这么简单呢,我们该如何定义?
string mid = ",middle part,"; Func<string, string> lambda = param => { param += mid; param += "and this was added to the string"; return param; }; Console.WriteLine(lambda("start of string"));
通过大括号包起来做一些复杂的事情。
闭包
通过lambda表达式可以访问lambda表达式外部的变量,就是闭包。
someVal是lambda表达式外的变量,在表达式内部进行了访问。
int someVal = 5; Func<int, int> f = x => x + someVal;
实际上对于表达式 x => x + someVal;编译器给它定义了一个匿名类;类中定义了有参构造方法,外部变量作为构造方法的参数传入到这个类中进行访问的。
public class AnonymousClass { private int someVal; public AnonymousClass(int someVal)//外部变量作为构造函数的参数。 { this.someVal = someVal; } public int AnonymousMethod(int x) { return x + someVal; } }
foreach闭包
主要是说明闭包在C#4.0和C#5.0对这个的改变。
var values = new List<int>() { 10, 20, 30 }; var funcs = new List<Func<int>>(); foreach(var val in values) { funcs.Add(() => val); } foreach(var f in funcs) { Console.WriteLine(f); }
在C#4.0中会输出30,30,30而在C#5.0中会输出10,20,30
我们回到C#高级编程9的数组中看到之前有说明foreach的原始逻辑:
foreach (var p in persons) { Console.WriteLine(p); } //通过IL中间语言生成后: IEnumerator<Person> enumerator = persons.GetEnumerator(); while (enumerator) { Person p = enumerator.Current; Console.WriteLine(p); }
实际上在C#4.0中enumerator.Current是定义在循环外部的,每次迭代都使用这个值。循环结束该变量就是最后的值。
因此在C#4.0中需要将 funcs.Add(() => val);改为var v=val;funcs.Add(() => v);
4.事件
事件基于委托,为委托提供了发布和订阅机制。
事件发布程序和侦听器
首先我们看看这个信息:
交通工具: 巴士
乘客甲: 乘坐了 巴士
交通工具: 的士
乘客甲: 乘坐了 的士
乘客乙: 乘坐了 的士
交通工具: 地铁
乘客乙: 乘坐了 地铁
首先可以看出通过程序执行了“交通工具: 巴士”,然后执行了“乘客甲: 乘坐了 巴士”;
接下来看这块代码:
var dealer = new CarDealer(); var michael = new Consumer("乘客甲"); WeakEventManager<CarDealer, CarInfoEventArgs>.AddHandler(dealer, "NewCarInfo", michael.NewCarIsHere);
dealer.NewCar("巴士");
CarDealer和Consumer分别是2个对象,
WeakEventManager 提供基本类中使用的事件管理 弱事件模式。 该管理器添加和移除的事件 (或回调) 也使用该模式的侦听器。
下面是AddHandler方法的定义:
public static void AddHandler(TEventSource source, string eventName, EventHandler<TEventArgs> handler);
source事件源;eventName是事件源对象里面的属性也就是事件名.handler是处理事件
执行的逻辑是这样的:
WeakEventManager.AddHandler做的事情是:
1)指定source对象中必须包含eventName属性,如果不存在该属性,会抛出异常。
2)为eventName属性定义了实现handler;也就是说 Func<string, EventArgs> sourceEvent = eventName => handler;
以这个为例:
WeakEventManager<CarDealer, CarInfoEventArgs>.AddHandler(dealer, "NewCarInfo", michael.NewCarIsHere); dealer.NewCar("巴士");
我们现在来想象一下CarDealer对象的定义:
1)首先必须存在一个类,类中必须有一个类型为EventHandler<TEventArgs>的属性,属性名必须是NewCarInfo,需要记住的是该属性是一个事件。
2)AddHandler方法为NewCarInfo属性指定了实现方法michael.NewCarIsHere,接下来就是进行调用NewCarInfo
3)我们定义一个方法NewCar,使用NewCar来调用NewCarInfo,那么如何调用NewCarInfo呢?请看下EventHandler<TEventArgs>需要传入哪些参数
public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);
4)由此我们得出了,再调用NewCarInfo方法时需要传入2个参数。sender是触发的事件对象,我们可以将dealer对象即NewCar方法中的this,还有一个就是TEventArgs e事件,这个事件也没有,这个事件是做什么用的呢,
主要是提供事件的一些信息。接下来我们定义这个类为CarInfoEventArgs,当然这个类必须继承TEventArgs ,否则不能作为NewCarInfo方法参数。然后我们提供一个Car的字符串属性,用于记录事件信息。
5)事件信息类也定义好了,我们需要调用NewCarInfo方法了,我们传入this还有new CarInfoEventArgs()对象。为了记录Car信息,我们将CarInfoEventArgs构造方法进行改造,传入Car参数。
6)然后我们就有了调用:
if (NewCarInfo != null) { NewCarInfo(this, new CarInfoEventArgs(car)); }
7)实际上到此为止,核心逻辑都写完了,但是还漏了一步。我们一直在说:NewCarInfo方法的调用和实现,但是NewCarInfo具体实现还没有。
8)我们再写一个类,类里面需要包含一个方法,方法必须符合EventHandler<TEventArgs>委托类型
public void NewCarIsHere(object sender, CarInfoEventArgs e) { Console.WriteLine("{0}: 乘坐了 {1}", name, e.Car); }
9)我们已经有了sender和e的值即第6点中的: this和new CarInfoEventArgs(car)
10)现在可以调用了。不好意思现在可以揭露代码的真相了.如果没有理解上面说的含义,没有关系,把下面的代码放到程序里面,调试一下吧。你就明白了。

static void Main(string[] args) { var dealer = new CarDealer(); var michael = new Consumer("乘客甲"); WeakEventManager<CarDealer, CarInfoEventArgs>.AddHandler(dealer, "NewCarInfo", michael.NewCarIsHere); dealer.NewCar("巴士"); }

public class CarInfoEventArgs : EventArgs { public CarInfoEventArgs(string car) { this.Car = car; } public string Car { get; private set; } } public class CarDealer { public event EventHandler<CarInfoEventArgs> NewCarInfo; public CarDealer() { } public void NewCar(string car) { Console.WriteLine("交通工具: {0}", car); if (NewCarInfo != null) { NewCarInfo(this, new CarInfoEventArgs(car)); } } }

public class Consumer { private string name; public Consumer(string name) { this.name = name; } public void NewCarIsHere(object sender, CarInfoEventArgs e) { Console.WriteLine("{0}: 乘坐了 {1}", name, e.Car); } }
现在结果出来了
交通工具: 巴士
乘客甲: 乘坐了 巴士
接下来修改一下运行代码:
static void Main(string[] args) { var dealer = new CarDealer(); var michael = new Consumer("乘客甲"); WeakEventManager<CarDealer, CarInfoEventArgs>.AddHandler(dealer, "NewCarInfo", michael.NewCarIsHere); dealer.NewCar("巴士"); var sebastian = new Consumer("乘客乙"); WeakEventManager<CarDealer, CarInfoEventArgs>.AddHandler(dealer, "NewCarInfo", sebastian.NewCarIsHere); dealer.NewCar("的士"); WeakEventManager<CarDealer, CarInfoEventArgs>.RemoveHandler(dealer, "NewCarInfo", michael.NewCarIsHere); dealer.NewCar("地铁"); }
给NewCarInfo事件多添加了一个事件处理(事件的实现)sebastian.NewCarIsHere
事件还是原来的事件NewCarInfo,AddHandler()方法除了给NewCarInfo事件指定了事件处理,还可以为该事件添加多个事件处理。
RemoveHandler()方法用来移除所添加的事件处理。必须指定事件对象dealer,事件名,以及事件处理,如果提供的事件处理不存在该事件对象中,将不会移除。
弱事件
通过事件、直接连接到发布程序和侦听器。垃圾回收有问题,如:侦听器不再直接引用。发布程序仍有一个引用。垃圾回收器不能清空侦听器占用的内存。
这种强连接可以使用弱事件解决。使用WeakEventManager作为发布程序和侦听器的中介。我们已经理解了关于发布和侦听器,下面的就好理解了。

class Program { static void Main() { var dealer = new CarDealer(); var michael = new Consumer("Michael"); WeakCarInfoEventManager.AddListener(dealer, michael); dealer.NewCar("Mercedes"); var sebastian = new Consumer("Sebastian"); WeakCarInfoEventManager.AddListener(dealer, sebastian); dealer.NewCar("Ferrari"); WeakCarInfoEventManager.RemoveListener(dealer, michael); dealer.NewCar("Red Bull Racing"); } }

public class WeakCarInfoEventManager : WeakEventManager { public static void AddListener(object source, IWeakEventListener listener) { CurrentManager.ProtectedAddListener(source, listener); } public static void RemoveListener(object source, IWeakEventListener listener) { CurrentManager.ProtectedRemoveListener(source, listener); } public static WeakCarInfoEventManager CurrentManager { get { WeakCarInfoEventManager manager = GetCurrentManager(typeof(WeakCarInfoEventManager)) as WeakCarInfoEventManager; if (manager == null) { manager = new WeakCarInfoEventManager(); SetCurrentManager(typeof(WeakCarInfoEventManager), manager); } return manager; } } protected override void StartListening(object source) { (source as CarDealer).NewCarInfo += CarDealer_NewCarInfo; } void CarDealer_NewCarInfo(object sender, CarInfoEventArgs e) { DeliverEvent(sender, e); } protected override void StopListening(object source) { (source as CarDealer).NewCarInfo -= CarDealer_NewCarInfo; } }

public class Consumer : IWeakEventListener { private string name; public Consumer(string name) { this.name = name; } public void NewCarIsHere(object sender, CarInfoEventArgs e) { Console.WriteLine("{0}: car {1} is new", name, e.Car); } bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e) { NewCarIsHere(sender, e as CarInfoEventArgs); return true; } }

public class CarInfoEventArgs : EventArgs { public CarInfoEventArgs(string car) { this.Car = car; } public string Car { get; private set; } } public class CarDealer { public event EventHandler<CarInfoEventArgs> NewCarInfo; public CarDealer() { } public void NewCar(string car) { Console.WriteLine("CarDealer, new car {0}", car); if (NewCarInfo != null) { NewCarInfo(this, new CarInfoEventArgs(car)); } } }
执行过程:
注册事件>添加事件>发布程序>事件侦听>执行事件

/* 1)WeakCarInfoEventManager.AddListener 2)WeakCarInfoEventManager.StartListening(object source) 3)dealer.NewCar("Mercedes"); 4)NewCarInfo(this, new CarInfoEventArgs(car)) 5)WeakCarInfoEventManager.CarDealer_NewCarInfo(object sender, CarInfoEventArgs e) 6)Consumer.ReceiveWeakEvent 7)Consumer.NewCarIsHere 8)WeakCarInfoEventManager.CarDealer_NewCarInfo执行完成 */
@author duanlaibao
@help C# Advanced programming.Nine
@date 13:31:36
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
· 上周热点回顾(2.17-2.23)