张子阳《.NET之美》样章 - C#中的委托和事件(完整)
### 知识点生成 #### 2.1 理解委托 ##### 2.1.1 将方法作为方法的参数 在面向对象编程语言中,如C#,我们通常将数据(对象)和行为(方法)封装在一起。然而,在某些情况下,我们需要将方法作为一种参数传递给另一个方法。这种做法在函数式编程中非常常见,而在C#中可以通过委托来实现。 **示例代码解析:** 在给定的部分内容中,作者首先定义了一个`GreetPeople`方法,该方法接收两个参数:一个是字符串类型的`name`,另一个是`Language`枚举类型的`lang`。`Language`枚举用于指定问候语的语言,例如: ```csharp public enum Language { English, Chinese } ``` 接着,`GreetPeople`方法根据传入的`Language`枚举值调用相应的问候方法。最初有两个问候方法:`EnglishGreeting`和`ChineseGreeting`,分别输出英文和中文问候语: ```csharp public void EnglishGreeting(string name) { Console.WriteLine("Morning, " + name); } public void ChineseGreeting(string name) { Console.WriteLine("早上好, " + name); } ``` 但是这种方法存在一个问题:如果需要支持更多语言,就需要不断地修改`Language`枚举和`GreetPeople`方法,这显然不是一种好的设计方案。因此,引入了将方法作为参数的概念。 **解决方法:** 为了解决上述问题,可以使用委托。委托允许我们将方法作为参数传递。具体实现方式如下: 1. **定义委托类型**:创建一个委托类型,该类型表示方法的签名。例如,对于问候方法,可以定义如下委托类型: ```csharp public delegate void GreetingDelegate(string name); ``` 2. **修改GreetPeople方法**:将委托类型作为参数传递给`GreetPeople`方法,并在方法内部调用这个委托。 ```csharp public void GreetPeople(GreetingDelegate greetMethod, string name) { // 执行其他逻辑... greetMethod(name); } ``` 3. **动态选择方法**:在调用`GreetPeople`方法时,可以动态地选择要传递的方法: ```csharp GreetPeople(EnglishGreeting, "Jimmy"); GreetPeople(ChineseGreeting, "Jimmy"); ``` 通过这种方式,我们可以在运行时决定使用哪种问候方法,而无需修改`GreetPeople`方法本身。 ##### 2.1.2 将方法绑定到委托 将方法绑定到委托的过程涉及几个步骤: 1. **定义委托类型**:首先定义一个委托类型,该类型与目标方法具有相同的签名。 2. **实例化委托**:创建委托类型的实例,并将目标方法绑定到这个实例上。 3. **调用委托**:通过调用委托实例来执行绑定的方法。 这种方法不仅可以提高代码的灵活性和可维护性,还可以轻松地实现动态调用不同方法的功能,这对于构建可扩展的系统非常有用。 #### 2.2 事件的由来 ##### 2.2.1 更好的封装性 事件是一种特殊的委托类型,主要用于通知机制。在C#中,当一个类想要向其他对象发送消息时,可以使用事件。这使得对象之间能够解耦,提高了代码的封装性和可重用性。 **示例:** 假设有一个名为`Button`的类,每当用户点击按钮时,希望执行一系列操作。使用事件可以很容易地实现这一点: 1. **定义事件**:在`Button`类中定义一个事件,例如: ```csharp public event EventHandler Click; ``` 2. **触发事件**:当用户点击按钮时,触发这个事件: ```csharp public void OnClick() { Click?.Invoke(this, EventArgs.Empty); } ``` 3. **订阅事件**:其他对象可以订阅此事件,以便在事件触发时执行某些操作: ```csharp button.Click += (sender, e) => { Console.WriteLine("Button clicked!"); }; ``` 通过这种方式,`Button`类不需要知道哪些对象对其点击感兴趣,这大大增强了系统的解耦合性和可维护性。 ##### 2.2.2 限制类型能力 除了提供更好的封装性之外,事件还帮助限制了类型的能力。当一个类型定义了一个事件时,它实际上是声明了一个接口,表明它可以通知其他对象发生了特定的事情。这有助于确保任何订阅事件的对象都遵循一定的约定,从而减少潜在的设计错误。 **示例:** 继续使用前面的`Button`类为例,我们可以进一步限制`Click`事件的行为: 1. **定义自定义事件参数**:为了传递更多信息,可以定义一个自定义的事件参数类: ```csharp public class ButtonClickedEventArgs : EventArgs { public int X { get; set; } public int Y { get; set; } } ``` 2. **修改事件定义**:将事件的参数类型改为自定义类型: ```csharp public event EventHandler<ButtonClickedEventArgs> Click; ``` 3. **触发事件时传递更多信息**: ```csharp public void OnClick(int x, int y) { Click?.Invoke(this, new ButtonClickedEventArgs { X = x, Y = y }); } ``` 4. **订阅事件并使用更多信息**: ```csharp button.Click += (sender, e) => { var args = e as ButtonClickedEventArgs; if (args != null) { Console.WriteLine($"Button clicked at ({args.X}, {args.Y})!"); } }; ``` 通过这种方式,事件不仅限于简单地通知其他对象,还可以传递更多的上下文信息,从而使订阅者能够做出更复杂的响应。 #### 2.3 委托的编译代码(将做大幅改进) 在这一节中,作者提到了委托的编译代码将会做大幅度的改进,虽然没有具体的细节,但可以推测作者可能会深入探讨委托在编译后的表现形式以及编译器是如何处理委托的。这对于理解委托的工作原理和性能影响至关重要。 #### 2.4 .NET框架中的委托和事件 ##### 2.4.1 范例说明 .NET框架中广泛使用了委托和事件。作者可能会通过一个或多个示例来展示这些概念的实际应用。这有助于读者更好地理解委托和事件如何在实际项目中发挥作用。 ##### 2.4.2 Observer设计模式简介 Observer模式是一种常见的设计模式,用于实现实体之间的松耦合。在这种模式下,多个观察者对象可以监听一个主题对象的状态变化,当主题对象的状态发生变化时,会自动通知所有注册的观察者。委托和事件是实现Observer模式的一种有效方式。 **示例:** 1. **定义主题类**:定义一个主题类,该类包含一个事件: ```csharp public class Subject { public event EventHandler<EventArgs> StateChanged; public void NotifyObservers() { StateChanged?.Invoke(this, EventArgs.Empty); } } ``` 2. **定义观察者类**:定义一个观察者类,该类订阅主题类的事件: ```csharp public class Observer { private Subject subject; public Observer(Subject subject) { this.subject = subject; subject.StateChanged += OnStateChanged; } private void OnStateChanged(object sender, EventArgs e) { Console.WriteLine("State has changed."); } } ``` 3. **使用Observer模式**: ```csharp var subject = new Subject(); var observer = new Observer(subject); // 触发事件 subject.NotifyObservers(); ``` 通过这种方式,当`Subject`的状态发生改变时,所有订阅了`StateChanged`事件的观察者都会被自动通知。 ##### 2.4.3 实现范例的Observer设计模式 作者可能会给出一个完整的Observer模式实现示例,包括如何定义主题、观察者以及如何订阅和取消订阅事件。 ##### 2.4.4 .Net框架中的委托与事件 .NET框架提供了许多内置的委托和事件类型,这些类型通常用于各种场景。例如,`EventHandler`委托和`EventArgs`类是.NET框架中最常用的事件处理机制之一。 **示例:** 1. **定义事件**: ```csharp public event EventHandler<MyEventArgs> CustomEvent; ``` 2. **触发事件**: ```csharp public void RaiseCustomEvent() { CustomEvent?.Invoke(this, new MyEventArgs()); } ``` 3. **定义自定义事件参数**: ```csharp public class MyEventArgs : EventArgs { public string Message { get; set; } } ``` 4. **订阅事件**: ```csharp myObject.CustomEvent += (sender, e) => { var args = e as MyEventArgs; if (args != null) { Console.WriteLine(args.Message); } }; ``` 通过使用.NET框架提供的标准委托和事件类型,可以简化事件处理过程,并提高代码的可读性和可维护性。 #### 2.5 委托进阶 ##### 2.5.1 为什么委托定义的返回值通常都为void? 委托定义的返回值通常设置为`void`的原因在于委托主要用于传递方法的引用,而这些方法通常是用来执行某些操作而不是返回值。例如,在事件处理中,事件通常不需要返回值,因为它们的主要目的是通知其他对象发生了一些事情。 **示例:** ```csharp public delegate void MyDelegate(); ``` 在上面的例子中,`MyDelegate`委托定义了一个没有参数并且没有返回值的方法引用类型。这样的委托非常适合用作事件处理器。 ##### 2.5.2 如何让事件只允许一个客户订阅? 有时可能需要限制事件只能有一个订阅者。这可以通过在事件定义时添加逻辑来实现,以确保只有一个订阅者可以订阅事件。 **示例:** ```csharp public event EventHandler Click; private EventHandler _clickHandler; public void AddClickHandler(EventHandler handler) { if (_clickHandler != null) { throw new InvalidOperationException("Click event is already subscribed."); } _clickHandler = handler; Click += _clickHandler; } public void RemoveClickHandler(EventHandler handler) { if (_clickHandler == handler) { Click -= _clickHandler; _clickHandler = null; } } ``` 在这个例子中,我们定义了一个私有的事件处理程序 `_clickHandler`,并在添加事件处理器时检查是否已经有处理程序。如果有,则抛出异常;如果没有,则添加处理程序。同样地,删除事件处理器时也会检查处理程序是否已经存在。 ##### 2.5.3 获得多个返回值与异常处理 在某些情况下,我们可能需要从委托调用中获取多个返回值或处理异常情况。这可以通过返回值类型、元组或异常处理机制来实现。 **示例:** 1. **使用元组返回多个值**: ```csharp public delegate Tuple<int, int> MyDelegate(string input); ``` 2. **使用异常处理**: ```csharp try { var result = myDelegate.Invoke(input); } catch (Exception ex) { // 处理异常 } ``` 通过这种方式,可以在委托调用中返回多个值,并优雅地处理可能出现的异常。 #### 2.6 订阅者方法超时的处理 当事件处理方法耗时过长时,可能会导致应用程序出现性能问题。为了防止这种情况,可以通过设定超时限制来处理长时间运行的事件处理方法。 **示例:** 1. **使用Task和CancellationToken**: ```csharp public async Task HandleEventAsync(CancellationToken token) { await Task.Delay(1000, token); // 处理事件 } ``` 2. **使用超时机制**: ```csharp CancellationTokenSource cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); CancellationToken token = cts.Token; try { await handleEventAsync(token); } catch (OperationCanceledException) { // 超时处理 } ``` 通过这种方式,可以在事件处理方法耗时过长时中断其执行,从而避免影响整个应用程序的性能。 #### 2.7 委托和方法的异步调用 在现代软件开发中,异步编程变得越来越重要。通过异步调用委托,可以提高应用程序的响应性和性能。 **示例:** 1. **使用async/await**: ```csharp public async Task MyAsyncDelegate(string input) { await Task.Delay(1000); // 异步处理 } ``` 2. **订阅异步事件**: ```csharp myObject.AsyncEvent += async (sender, e) => { await Task.Delay(1000); // 异步处理 }; ``` 通过异步调用委托,可以有效地管理资源,避免阻塞主线程,从而提高用户体验。 #### 2.8 不使用委托实现Observer模式 虽然使用委托和事件是实现Observer模式的一种常见方式,但在某些情况下,可能由于性能考虑或其他原因而不使用委托。这时,可以通过其他方式来实现Observer模式。 **示例:** 1. **定义观察者列表**: ```csharp public class Subject { private List<Observer> observers = new List<Observer>(); public void Attach(Observer observer) { observers.Add(observer); } public void Detach(Observer observer) { observers.Remove(observer); } public void Notify() { foreach (var observer in observers) { observer.Update(this); } } } ``` 2. **定义观察者接口**: ```csharp public interface IObserver { void Update(ISubject subject); } ``` 3. **定义观察者类**: ```csharp public class ConcreteObserver : IObserver { public void Update(ISubject subject) { // 更新逻辑 } } ``` 通过这种方式,我们使用了观察者列表和更新方法来替代委托和事件,实现了Observer模式的基本功能。 #### 2.9 总结 通过本章的学习,我们深入了解了C#中的委托和事件,包括它们的基本概念、应用场景以及实现技巧。委托和事件是.NET框架中非常重要的组成部分,掌握它们不仅能够帮助我们编写更加灵活和高效的代码,还能更好地理解和利用.NET框架提供的各种功能。此外,通过对Observer模式的学习,我们了解到委托和事件在实现该模式中的作用,以及在某些特殊场景下如何不使用委托来实现同样的目的。委托和事件是每个C#开发者都应该掌握的核心技能之一。
- 粉丝: 5
- 资源: 4
- 我的内容管理 展开
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
最新资源
- Lawrence C. Evans Partial Differential Equations.djvu
- CFA知识点梳理系列:CFA Level II, Reading 4 Big Data Projects
- 专业问题 · 语雀.mhtml
- 基于Vue+TP6的B2B2C多场景电商商城设计源码
- 基于小程序的研知识题库小程序源代码(java+小程序+mysql).zip
- 基于小程序的微信小程序的点餐系统源代码(java+小程序+mysql).zip
- 基于小程序的宿舍管理小程序源代码(java+小程序+mysql).zip
- 基于小程序的小区服务系统源代码(python+小程序+mysql).zip
- QT项目之中国象棋人工智能
- 基于小程序的疫情核酸预约小程序源代码(java+小程序+mysql).zip
- 1
- 2
前往页