C# 委托类型与使用详解

79

委托在C#中类似于函数指针,用来封装方法,允许将方法作为参数传递或者异步回调。比如Action、Func,还有事件和匿名方法。

需要掌握声明委托的关键字delegate、内置的Action和Func

需要注意多播委托,可以组合多个方法,按顺序执行。比如用+=添加方法,用-=移除,注意返回值的问题,如果有多个方法带返回值,最后执行的结果会被返回,前面的可能会被覆盖,所以通常多播委托用于void返回类型的方法。

事件和委托的关系也很重要,事件是基于委托的,但事件有封装,更安全。比如只能在声明事件的类中触发,而委托可以在外部调用。

匿名方法和Lambda表达式也是委托的一部分,可以简化代码。比如用delegate关键字或者lambda来直接写方法体。这会让代码更简洁,特别是在LINQ查询或者事件处理中常用。

常见问题,比如空委托需要检查是否为null,或者多播委托的执行顺序问题。还有协变和逆变,委托参数类型的灵活性,比如参数可以是基类或者派生类,返回类型可以是派生类或者基类。

委托是什么

在 C# 中,委托(Delegate) 是一种类型安全的函数指针机制,用于封装方法(函数),允许将方法作为参数传递、回调或异步调用。它是实现事件驱动编程和回调模式的核心技术。

委托的概念

定义:委托是一种引用类型,定义了方法的签名(参数类型和返回值类型)。

作用:将方法动态绑定到变量,实现方法的间接调用。

特点:

  • 类型安全(编译时检查方法签名)。

  • 支持多播(通过 += 和 -= 组合多个方法)。

  • 可以匿名使用(通过 Lambda 表达式或匿名方法)

委托的声明与使用

声明委托类型

// 定义一个委托类型,不带参数返回 void 的方法
public delegate void MyDelegate();

// 定义一个委托类型,带参数和返回值的委托
public delegate int CalculateDelegate(int a, int b);

实例化委托

// 普通方法的示例
public void SayHello() => Console.WriteLine("Hello!");
public int Add(int a, int b) => a + b;

// 实例化委托并绑定方法
MyDelegate del1 = new MyDelegate(SayHello);
CalculateDelegate del2 = Add;

// 简化写法
MyDelegate del3 = SayHello;
CalculateDelegate del4 = Add;

调用委托

del1();       // 输出 "Hello!"
int result = del2(3, 5);  // result = 8

多播委托(Multicast Delegate)

通过 += 和 -= 运算符组合多个方法。

调用时按添加顺序依次执行方法。

适用场景:通常用于返回值为 void 的方法(非 void 返回值的方法会被覆盖)

public delegate void LoggerDelegate(string message);

public void LogToConsole(string message) => Console.WriteLine($"Console: {message}");
public void LogToFile(string message) => File.WriteAllText("log.txt", message);

// 组合多个方法
LoggerDelegate logger = LogToConsole;
logger += LogToFile;

// 调用时依次执行 LogToConsole 和 LogToFile
logger("Something happened!");

// 移除方法
logger -= LogToFile;

内置的委托类型

C# 提供了内置的泛型委托,无需手动定义

Action:无返回值的方法

Action action = () => Console.WriteLine("Action");
Action<int, string> actionWithParams = (x, s) => Console.WriteLine($"{x}, {s}");

Func:有返回值的方法

Func<int> func = () => 42;
Func<int, int, int> add = (a, b) => a + b;

Predicate<T>:返回 bool 的方法(用于条件判断)

Predicate<int> isEven = x => x % 2 == 0;

匿名方法与 Lambda 表达式

匿名方法(C# 2.0)

MyDelegate del = delegate() { Console.WriteLine("Anonymous method"); };

Lambda 表达式(C# 3.0+)

MyDelegate del = () => Console.WriteLine("Lambda");
Func<int, int, int> multiply = (a, b) => a * b;

委托与事件

事件(Event) 是基于委托的封装,提供更安全的订阅/取消订阅机制。

事件只能在声明它的类中触发(避免外部直接调用委托)。

public class Button
{
    public event EventHandler Clicked; // EventHandler 是内置委托

    public void Press()
    {
        Clicked?.Invoke(this, EventArgs.Empty); // 安全调用(检查 null)
    }
}

// 使用
Button btn = new Button();
btn.Clicked += (sender, e) => Console.WriteLine("Button clicked!");
btn.Press();

委托的常见用途

回调模式:将方法传递给异步操作或算法。

事件处理:GUI 编程(如按钮点击)。

LINQ 查询:通过 Func 和 Action 实现灵活的数据操作。

异步编程:结合 BeginInvoke/EndInvoke(旧模式)或 Task(新模式)。

注意事项

空委托检查:调用前需检查是否为 null(可用 ?.Invoke() 简化)。

协变与逆变:委托支持参数类型和返回类型的灵活转换(C# 4.0+)。

性能:委托调用比直接方法调用略慢,但在大多数场景中可忽略。

通过委托,C# 实现了高度灵活的函数抽象,是理解事件、Lambda 和异步编程的基础