欢迎光临
我们一直在努力

C#学习笔记(8)——委托与事件

委托

在 C# 中,委托(Delegate) 是一种类型安全的函数指针,它允许将方法作为参数传递给其他方法。
我们的核心目标是将方法作为参数传递,而不是将数据作为参数传递。类似于 C 或 C++ 中函数的指针。委托(Delegate) 是存有对某个方法的引用的一种引用类型变量,引用可在运行时被改变。
所有的委托(Delegate)都派生自 System.Delegate 类。

声明委托

委托是一个引用类型,它定义了一个方法签名,可以用于存储指向该签名的方法。通过委托,你可以调用其他类中的方法。

public delegate <return type> <delegate–name> <parameter list>;

例如以下代码,我们定义一个接受两个整数并返回一个整数的委托:

public delegate int MathOperation(int a, int b);

委托的实例化

委托的实例化与类的实例化类似,你可以使用 new 关键字来创建一个委托的实例。

MathOperation add = new MathOperation(Add);

在C#中,也可以对上面的代码进行简写,这种写法更常用。(语法糖)

MathOperation add = Add;

因为委托类型在赋值时,编译器能根据左边的类型自动推断出:
“右边这个 Add 是一个方法组(method group),我要把它转换成对应的委托对象”

一个委托的代码示例

using System;

namespace DelegateDemo
{
class Program
{
// 1) 定义一个委托类型:要求方法必须是 (int, int) -> int
delegate int Calc(int a, int b);

static void Main(string[] args)
{
// 2) 把方法赋值给委托(委托就像“方法的引用”)
Calc c1 = Add;
Calc c2 = Sub;

// 3) 通过委托调用方法
Console.WriteLine("=== 直接调用委托 ===");
Console.WriteLine("Add: " + c1(10, 3)); // 13
Console.WriteLine("Sub: " + c2(10, 3)); // 7

// 4) 把委托当参数传进去(核心用途:把“行为”传递)
Console.WriteLine("\\n=== 委托作为参数 ===");
Console.WriteLine("DoCalc(Add): " + DoCalc(5, 2, Add)); // 7
Console.WriteLine("DoCalc(Sub): " + DoCalc(5, 2, Sub)); // 3

// 5) 也可以用 Lambda 表达式直接写一个方法给委托
Calc mul = (x, y) => x * y;
Console.WriteLine("\\n=== Lambda 作为委托 ===");
Console.WriteLine("Mul: " + mul(5, 2)); // 10

Console.WriteLine("\\n按任意键退出…");
Console.ReadKey();
}

// 被委托引用的方法 1
static int Add(int x, int y)
{
return x + y;
}

// 被委托引用的方法 2
static int Sub(int x, int y)
{
return x – y;
}

// 委托作为参数:你传什么算法进来,它就用什么算法算
static int DoCalc(int a, int b, Calc calc)
{
return calc(a, b);
}
}
}

这个例子展示了委托的基本用法,包括定义委托类型、实例化委托、使用委托调用方法、将委托作为参数传递以及使用 Lambda 表达式直接写一个方法给委托。
委托也可以在类外声明,只是需要在类外声明时,需要加上 public 关键字。

public delegate int MathOperation(int a, int b);

如果不加上 public 关键字,那么委托只能在当前类中使用。

内置委托

C# 提供了一些内置的委托类型,用于常见的场景。

  • Action:无返回值的委托。
  • Func<T>:有返回值的委托。
  • Predicate<T>:返回 bool 类型的委托。
Action(无返回值)

Action 委托是一个无返回值的委托类型。它可以用于表示没有参数的方法,或者参数类型为 void 的方法。

Action action = () => Console.WriteLine("Hello World!");
action(); // 输出:Hello World!

Func(有返回值)

Func<T> 委托是一个有返回值的委托类型。它可以用于表示有参数的方法,并且参数类型可以是任意数量的。

Func<int, int, int> add = (a, b) => a + b;
int result = add(2, 3); // result = 5

Func 的最后一个泛型参数是返回值类型,其他泛型参数是参数类型。

Predicate(返回 bool 类型)

Predicate<T> 委托是一个返回 bool 类型的委托类型。它可以用于表示有参数的方法,参数类型为 T,返回值为 bool。

Predicate<int> isEven = x => x % 2 == 0;
bool result = isEven(4); // result = true

例子

我们可以使用Func委托将我们上面代码中的委托声明进行改写
我们之前声明了委托

// 1) 定义一个委托类型:要求方法必须是 (int, int) -> int
delegate int Calc(int a, int b);

我们可以改写为

// 1) 定义一个委托类型:要求方法必须是 (int, int) -> int
Func<int, int, int> Calc;

事件

C# 事件(Event)是一种成员,用于将特定的事件通知发送给订阅者。事件通常用于实现观察者模式,它允许一个对象将状态的变化通知其他对象,而不需要知道这些对象的细节。

就像是一个广播,当某个事件发生时,所有订阅了这个事件的对象都会收到通知。

事件和委托的关系

事件是基于委托实现的。事件是一个特殊的委托,它只能在类中声明,并且只能在类的内部调用。
委托可以这样用:

public Action OnSomething;

但这有风险:外部可以直接把它清空或乱赋值:

obj.OnSomething = null; // 外部直接干掉所有订阅者 ❌
obj.OnSomething(); // 外部还能直接触发事件 ❌

所以 C# 提供了 event:

public event Action OnSomething;

event的意义是:

  • 只能在类的内部调用。
  • 外部不能直接赋值,只能通过 += 和 -= 来订阅和取消订阅。

通过事件使用委托

声明事件(Event)

在类的内部声明事件,首先必须声明该事件的委托类型。例如:

public delegate void BoilerLogHandler(string status);

然后,声明事件本身,使用 event 关键字:

public event BoilerLogHandler OnBoilerLog;

触发事件

当事件发生时,需要触发事件。触发事件的代码通常在类的内部,例如在某个方法中。

private void Log(string status)
{
OnBoilerLog?.Invoke(status);
}

在上面的代码中,OnBoilerLog?.Invoke(status) 用于触发事件。如果没有订阅者,OnBoilerLog 为 null,调用 Invoke 方法会安全地忽略调用。

订阅事件

外部代码可以订阅事件,例如:

boiler.OnBoilerLog += LogToConsole;

在上面的代码中,LogToConsole 是一个方法,它符合 BoilerLogHandler 委托的签名。当 boiler 对象触发 OnBoilerLog 事件时,LogToConsole 方法会被调用。

取消订阅事件

当外部代码不再需要接收事件通知时,应该取消订阅事件,例如:

boiler.OnBoilerLog -= LogToConsole;

事件的一个完整例子

using System;

class Publisher
{
// 事件类型:无参无返回
public event Action SomethingHappened;

public void DoWork()
{
Console.WriteLine("Publisher: 我开始工作了…");

// 触发事件(通知所有订阅者)
SomethingHappened?.Invoke();
}
}

class Program
{
static void Main()
{
var p = new Publisher();

// 订阅事件(+=)
p.SomethingHappened += () => Console.WriteLine("Subscriber1: 我收到通知了!");
p.SomethingHappened += () => Console.WriteLine("Subscriber2: 我也收到通知了!");

// 触发事件(由发布者内部触发)
p.DoWork();

Console.ReadKey();
}
}

运行结果类似

Publisher: 我开始工作了…
Subscriber1: 我收到通知了!
Subscriber2: 我也收到通知了!

这个例子中,

  • Publisher 类声明了一个事件 SomethingHappened,它的委托类型是 Action(无参无返回)。
  • Program 类订阅了 Publisher 类的 SomethingHappened 事件。
  • Program 类调用 Publisher 类的 DoWork 方法,触发 SomethingHappened 事件。
  • 所有订阅了 SomethingHappened 事件的方法都会被调用。

请您注意这里面的一些误区,订阅事件的主体是事件,而不是需要订阅的对象,您可以理解为事件有一个“事件中心”,事件中心负责管理所有订阅者并在事件触发时调用它们。

EventHandler

EventHandler 是一个预定义的委托类型,用于处理事件。它的签名是 (object sender, EventArgs e),其中 sender 是触发事件的对象,e 是事件数据。

public event EventHandler SomethingHappened;

在上面的代码中,SomethingHappened 事件的委托类型是 EventHandler。当事件触发时,EventHandler 会自动传递事件源和事件数据。
我们来看一个完整的例子:

using System;

class MyButton
{
// 标准事件:无额外信息
public event EventHandler Clicked;

public void Click()
{
Console.WriteLine("MyButton: Click() 被调用");

// 触发事件
Clicked?.Invoke(this, EventArgs.Empty);
}
}

class Program
{
static void Main()
{
var btn = new MyButton();

// 订阅事件(绑定处理方法)
btn.Clicked += OnButtonClicked;

btn.Click();

Console.ReadKey();
}

static void OnButtonClicked(object sender, EventArgs e)
{
Console.WriteLine("订阅者: 收到 Clicked 事件");

// sender 就是触发事件的对象(这里是 btn)
Console.WriteLine("sender 类型:" + sender.GetType().Name);
}
}

小结

  • 委托(Delegate)=“类型安全的方法指针”,用来把“行为”当成参数传递;

    • 自定义:delegate int Calc(int a,int b);
    • 系统内置:Action / Func / Predicate,优先使用内置。
  • 事件(Event)=“受控的委托”,只能在类内部触发,外部只能 += 订阅、-= 取消,防止误操作;

    • 简单场景:public event Action Something;
    • 标准场景:public event EventHandler Something; 自动携带 sender 与 EventArgs。
  • 典型流程
    发布者 → 声明事件 → 在合适时机 ?.Invoke()
    订阅者 → += 注册方法 → 收到通知后执行回调。

  • 赞(0)
    未经允许不得转载:171主机测评 » C#学习笔记(8)——委托与事件
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址