CSharp委托学习笔记

一、什么是委托

委托(Delegate)是 C# 中一种类型安全的函数指针。它可以把方法当作参数进行传递,或者将方法赋值给变量。

委托可以理解为一个方法的容器:定义委托变量,将符合条件的方法“装”进去,然后通过委托变量调用方法。

类比
委托就像一个遥控器:遥控器本身不是设备,但可以控制设备。

  • 定义委托类型 = 设计遥控器
  • 实例化委托 = 绑定具体设备
  • 调用委托 = 按下按钮执行操作

为什么需要委托

  • 解耦:调用方不直接依赖具体方法
  • 回调:将方法作为参数传递
  • 事件驱动:事件基于委托实现
  • 灵活性:运行时动态决定调用哪个方法

二、委托的声明

使用 delegate 关键字声明委托类型,语法类似方法定义但没有方法体。

// 无参数、返回 void 的委托
public delegate void MyDelegate();

// 两个 int 参数、返回 int 的委托
public delegate int CalculateDelegate(int x, int y);

委托声明定义的是一个类型,之后可以用它创建变量。


三、实例化委托并使用

创建委托实例时需要传入符合签名的方法,可以使用 new 关键字或直接赋值方法名。

static int Add(int a, int b) => a + b;
static int Multiply(int a, int b) => a * b;

CalculateDelegate calc = new CalculateDelegate(Add);
// 简写:CalculateDelegate calc = Add;

int result = calc(3, 5);        // 8
Console.WriteLine(result);

calc = Multiply;
Console.WriteLine(calc(3, 5));  // 15

委托调用方式:

  • calc(3, 5) 直接调用
  • calc.Invoke(3, 5) 使用 Invoke 方法

四、多播委托

一个委托实例可以绑定多个方法,调用时按添加顺序依次执行。使用 += 添加,-= 移除。

public delegate void NotifyDelegate(string message);

static void SendEmail(string msg) => Console.WriteLine($"邮件通知: {msg}");
static void SendSMS(string msg)   => Console.WriteLine($"短信通知: {msg}");
static void SendLog(string msg)   => Console.WriteLine($"日志记录: {msg}");

NotifyDelegate notify = SendEmail;
notify += SendSMS;
notify += SendLog;

notify("系统更新完成");
// 输出:
// 邮件通知: 系统更新完成
// 短信通知: 系统更新完成
// 日志记录: 系统更新完成

notify -= SendSMS;
notify("系统再次更新");
// 输出:
// 邮件通知: 系统再次更新
// 日志记录: 系统再次更新

注意:多播委托通常返回 void。若有返回值,多播时只返回最后一个方法的返回值。


五、内置泛型委托:Func 与 Action

.NET 提供常用的泛型委托,大多数情况无需自定义委托。

1. Action —— 无返回值

表示没有返回值的方法,可接受 0 到 16 个参数。

Action sayHello = () => Console.WriteLine("Hello!");
sayHello();

Action<string, int> showInfo = (name, age) =>
{
    Console.WriteLine($"姓名: {name}, 年龄: {age}");
};
showInfo("小明", 20);

2. Func —— 有返回值

最后一个类型参数是返回值类型,前面的是参数类型。

Func<int> getRandom = () => new Random().Next(1, 100);
Console.WriteLine(getRandom());

Func<int, int, int> add = (a, b) => a + b;
Console.WriteLine(add(3, 7));   // 10

Func<string, bool> isNullOrEmpty = s => string.IsNullOrEmpty(s);
Console.WriteLine(isNullOrEmpty("test")); // False

3. Predicate —— 返回 bool 的委托

接受一个参数并返回 bool,常用于筛选条件。

Predicate<int> isPositive = x => x > 0;
Console.WriteLine(isPositive(5));   // True
Console.WriteLine(isPositive(-3));  // False

Predicate<T>Func<T, bool> 功能相同。


六、匿名方法与 Lambda 表达式

匿名方法(C# 2.0)

CalculateDelegate calc = delegate(int a, int b)
{
    return a - b;
};
Console.WriteLine(calc(10, 4));  // 6

Lambda 表达式(C# 3.0+)

CalculateDelegate calc2 = (a, b) => a - b;
Console.WriteLine(calc2(10, 4));  // 6

Action<string> print = msg => Console.WriteLine($"消息: {msg}");
print("Hello Lambda");

Func<string> getTime = () => DateTime.Now.ToLongTimeString();
Console.WriteLine(getTime());

现代 C# 推荐使用 Lambda 表达式创建委托实例。


七、委托与事件的区别

事件是委托的特殊用法,封装了委托,限制外部代码直接调用或清空委托链。

特性 委托 事件
声明方式 delegate 关键字 event 关键字 + 委托类型
外部调用 可以直接调用 只能在声明事件的类内部调用
外部赋值 可以使用 = 覆盖 只能使用 +=-=
用途 回调、方法传递 发布-订阅模式
public delegate void EventHandler(string message);

public event EventHandler OnMessageReceived;

// 触发事件(类内部)
OnMessageReceived?.Invoke("有新消息");

使用事件而非公开委托是为了防止外部随意赋值或清空委托链,提供更好的封装性。


八、总结

  • 委托是方法的类型,可以把方法赋值给委托变量。
  • 使用 delegate 声明委托类型,+= / -= 实现多播。
  • Action 用于无返回值方法,Func 用于有返回值方法。
  • Lambda 表达式是创建委托实例的简洁方式。
  • 事件基于委托,用于实现安全的发布-订阅模式。

一句话记忆:委托 = 方法的“变量”,可以把方法当参数传,也可以把多个方法串在一起调用。


九、相关面试题

1. 什么是委托?它和函数指针有什么区别?

委托是 C# 中类型安全的函数指针,可以封装一个或多个方法,并支持多播。与 C/C++ 函数指针相比,委托是类型安全的、面向对象的,且支持多播和异步调用。

2. 委托和事件有什么区别?

事件是基于委托的封装,外部只能订阅/退订(+=/-=),不能直接调用或赋值,提高了安全性。委托可以被外部直接调用和覆盖。

3. 什么是多播委托?如何实现?

多播委托是一个委托实例可以绑定多个方法,调用时按添加顺序依次执行。使用 += 添加方法,-= 移除方法。通常用于通知、回调等场景。

4. Action 和 Func 的区别是什么?

Action 表示无返回值的方法,可接受 0 到 16 个参数。Func 表示有返回值的方法,最后一个类型参数为返回值类型,可接受 0 到 16 个输入参数。

5. 什么是匿名方法和 Lambda 表达式?它们与委托的关系?

匿名方法是 C# 2.0 引入的简化委托实例化的语法,Lambda 表达式是 C# 3.0 引入的更简洁写法。它们都是创建委托实例的便捷方式,本质上都会生成一个委托对象。

6. 委托可以指向静态方法吗?实例方法呢?

可以。委托既可以指向静态方法,也可以指向实例方法。指向实例方法时,委托会持有对象引用,调用时自动绑定到该实例。

7. 为什么委托比直接调用方法更灵活?

委托将方法的调用与实现解耦,允许在运行时动态指定要调用的方法,支持回调、事件、插件式架构等场景,提高代码的可扩展性和可维护性。

8. 什么是闭包?Lambda 表达式中的闭包有什么陷阱?

闭包是指 Lambda 表达式或匿名方法捕获了外部变量。陷阱:循环中捕获循环变量时,可能导致所有委托共享同一个变量值。C# 5.0 后 foreach 循环变量每次迭代都会创建新变量,但 for 循环仍需注意。

9. 委托的 Invoke 和 BeginInvoke 有什么区别?

Invoke 是同步调用,会阻塞当前线程直到方法执行完成。BeginInvoke 是异步调用,在后台线程执行方法,通过 IAsyncResult 获取结果。现代 .NET 推荐使用 Taskasync/await 替代 BeginInvoke

10. 如何自定义一个带返回值的多播委托?调用时返回值是什么?

多播委托可以有返回值,但调用时只返回最后一个方法的返回值,前面的返回值会被丢弃。若需要获取所有返回值,可以使用 GetInvocationList() 逐个调用。

posted @ 2026-08-18 15:02  春煦景明  阅读(5)  评论(1)    收藏  举报