委托与事件
委托
在 C# 中,委托(Delegate)是一种类型安全的对象,它定义了一种引用方法的方式。
委托可以存储对方法的引用,并且能够在运行时调用这些方法。
委托是一种类型,类似于 C++ 中的函数指针,但它更加安全和灵活。
委托的定义
委托是 C# 中的引用类型,定义了一种方法签名。
语法:
public delegate 返回类型 委托名(参数列表);
示例:
// 定义委托
public delegate void PrintDelegate(string message);
class Program
{
// 定义一个方法与委托签名相匹配
public static void PrintMessage(string message)
{
Console.WriteLine(message);
}
static void Main(string[] args)
{
// 实例化委托,并将方法分配给它
PrintDelegate print = new PrintDelegate(PrintMessage);
// 通过委托调用方法
print("Hello, World!");
}
}
特性:
- 类型安全:委托在编译时检查方法的签名(返回类型和参数),保证类型安全。
- 多播委托:委托可以引用多个方法,并依次调用它们。
- 异步调用:委托支持异步调用,通过 BeginInvoke 和 EndInvoke 可以异步执行方法。
- 封装方法:委托提供了一种将方法作为参数传递的方式,可以实现灵活的回调机制。
C# 中有三种主要的委托类型:
- 单播委托:只引用一个方法。
- 多播委托:引用多个方法,依次调用它们。
- 泛型委托:C# 提供了几个常用的泛型委托,如 Action<> 和 Func<>,用于简化委托的定义。
单播委托
单播委托(Single-cast Delegate)是指只引用一个方法的委托。
示例:
public delegate int CalculateDelegate(int x, int y);
class Program
{
public static int Add(int x, int y) => x + y;
public static int Subtract(int x, int y) => x - y;
static void Main(string[] args)
{
// 创建单播委托,引用 Add 方法
CalculateDelegate calculate = Add;
Console.WriteLine("Add: " + calculate(10, 5)); // 输出: 15
// 重新指向 Subtract 方法
calculate = Subtract;
Console.WriteLine("Subtract: " + calculate(10, 5)); // 输出: 5
}
}
特性:
- 只能引用一个方法:单播委托只能存储对一个方法的引用,调用时会执行该方法。
- 强类型安全:单播委托确保方法签名与委托类型匹配,因此编译时进行类型检查。
- 灵活性:即使是单播委托,依然可以通过动态的方式指定不同的方法,提供了一定的灵活性。
多播委托
委托可以引用多个方法,称为多播委托。通过 += 操作符可以将多个方法添加到一个委托中,委托会依次调用这些方法。
示例:
public delegate void PrintDelegate(string message);
class Program
{
public static void PrintMessage1(string message)
{
Console.WriteLine("Message 1: " + message);
}
public static void PrintMessage2(string message)
{
Console.WriteLine("Message 2: " + message);
}
static void Main(string[] args)
{
// 创建委托实例并关联多个方法
PrintDelegate print = PrintMessage1;
print += PrintMessage2;
// 调用委托,依次执行 PrintMessage1 和 PrintMessage2
print("Hello!");
}
}
特性:
- 引用多个方法:多播委托可以引用多个方法,每次调用委托时,所有被引用的方法都会依次执行。
- 顺序执行:委托按添加的顺序依次执行其引用的方法。
- 返回值处理:如果委托有返回值,只有最后一个方法的返回值会被保留,前面的返回值会被忽略。
- 删除方法:可以使用 -= 操作符从多播委托中移除方法。
多播委托的应用场景:
- 事件处理:多播委托通常用于事件机制中,一个事件可能有多个处理程序(事件处理器)。
- 通知机制:在需要广播消息或通知多个接收者时,可以使用多播委托来调用多个处理函数。
- 链式调用:当需要依次执行一系列操作时,可以使用多播委托。
泛型委托
泛型委托是指在 C# 中使用泛型类型参数定义的委托,它允许委托在调用时对不同的数据类型进行操作,而无需为每种类型定义不同的委托类型。泛型委托通过在委托声明中引入类型参数,使得同一个委托可以适用于多种类型。
常见的泛型委托类型 C# 提供了两种常用的泛型委托类型:
- Func<T, TResult>:表示有返回值的委托。
- Action<T>:表示没有返回值的委托。
应用场景
- 泛型算法:当算法逻辑与类型无关时,可以使用泛型委托实现通用的算法。
- 回调机制:泛型委托用于事件回调机制时,可以处理不同类型的数据,而不必为每种类型定义不同的委托。
- LINQ:在 LINQ 查询中,泛型委托(如 Func 和 Predicate)被广泛用于操作集合。
更多应用参考lambda表达式和匿名方法
Func<T, TResult>
Func 是一个有返回值的泛型委托,最多可以有 16 个输入参数,最后一个泛型参数是返回类型。
Func<int, int, int> add = (x, y) => x + y;
int result = add(3, 4); // 输出: 7
Console.WriteLine(result);
Action<T>
Action 是一个没有返回值的泛型委托,最多可以有 16 个输入参数,但不返回任何结果。
Action<string> printMessage = message => Console.WriteLine(message);
printMessage("Hello from Action!");
自定义泛型委托
除了使用内置的 Func、Action 和 Predicate,你还可以自己定义泛型委托。
public delegate T Calculate<T>(T x, T y);
class Program
{
static int Add(int x, int y) => x + y;
static double Multiply(double x, double y) => x * y;
static void Main()
{
Calculate<int> add = Add;
Console.WriteLine(add(3, 4)); // 输出: 7
Calculate<double> multiply = Multiply;
Console.WriteLine(multiply(2.5, 4.0)); // 输出: 10.0
}
}
事件
事件(Event)是 C# 中的一种特殊成员,用于封装委托,并用于发布-订阅模式的实现。
事件通常用于通知机制,让一个对象在某个动作发生时,通知其它对象(事件监听器)。
事件本质上是对委托的封装,但提供了更严格的访问控制。
事件的特点
- 发布-订阅模式:事件是发布-订阅模式的核心,允许一个对象(事件发布者)通知多个对象(事件订阅者)。
- 基于委托:事件基于委托,每个事件都关联一个委托,事件的触发实际上是委托的调用。
- 访问控制:事件只能在声明它的类内部触发(调用),而在类外部只能订阅或取消订阅事件,不能直接触发事件。
- 类型安全:事件可以确保订阅者只能传入与事件类型匹配的方法,提供了类型安全性。
应用场景
- UI 编程:事件广泛用于图形用户界面(GUI)编程中,按钮点击、输入框变化等都会触发事件。
- 通知机制:在复杂的业务逻辑中,事件用于通知不同模块或组件执行相应的操作。
- 异步编程:事件通常与异步操作相关联,异步完成时触发事件通知。
事件的使用
定义事件需要定义一个委托,事件使用该委托作为它的类型。
可以使用内置的 EventHandler 委托,也可以定义自定义的委托。
在事件发布者类之外,可以通过事件对象订阅该事件。订阅者需要传入与事件委托签名匹配的方法。
内置事件委托
C# 提供了内置的事件委托 EventHandler,用于定义没有返回值并接受两个参数的事件。第一个参数是事件的发送者,第二个参数是事件数据。
public class Publisher
{
// 使用内置的 EventHandler 委托
public event EventHandler<EventArgs> NotifyEvent;
public void Notify()
{
// 触发事件,并传入事件发送者和事件数据
NotifyEvent?.Invoke(this, EventArgs.Empty);
}
}
public class Subscriber
{
public void OnNotifyReceived(object sender, EventArgs e)
{
Console.WriteLine("Event received.");
}
}
class Program
{
static void Main(string[] args)
{
Publisher publisher = new Publisher();
Subscriber subscriber = new Subscriber();
// 订阅事件
publisher.NotifyEvent += subscriber.OnNotifyReceived;
// 触发事件
publisher.Notify();
}
}
自定义事件参数
如果需要传递更多的事件数据,可以定义自己的事件参数类,继承自 EventArgs。
// 自定义事件参数类
public class NotifyEventArgs : EventArgs
{
public string Message { get; set; }
public NotifyEventArgs(string message)
{
Message = message;
}
}
public class Publisher
{
// 使用自定义的事件参数类型
public event EventHandler<NotifyEventArgs> NotifyEvent;
public void Notify(string message)
{
// 触发事件,并传递自定义的事件参数
NotifyEvent?.Invoke(this, new NotifyEventArgs(message));
}
}
public class Subscriber
{
public void OnNotifyReceived(object sender, NotifyEventArgs e)
{
Console.WriteLine("Event received with message: " + e.Message);
}
}
class Program
{
static void Main(string[] args)
{
Publisher publisher = new Publisher();
Subscriber subscriber = new Subscriber();
// 订阅事件
publisher.NotifyEvent += subscriber.OnNotifyReceived;
// 触发事件
publisher.Notify("Hello, Custom EventArgs!");
}
}
取消对事件的监听
可以使用 -= 操作符可以从事件中移除订阅者,取消对事件的监听。
publisher.NotifyEvent -= subscriber.OnNotifyReceived;