C# 与 .NET 中的委托:把方法装进变量里

如果你学过 C 语言,大概对函数指针有些印象------那个既强大又危险的东西,稍不留神就会踩进类型错误的坑里。C# 的委托,可以理解为函数指针的"文明化改造":同样是对方法的引用,但编译器全程把关类型安全,还顺手附赠了多播、事件等一整套能力。

本质上,委托是一个类型。它定义了一份"签名契约"------规定方法必须接受什么参数、返回什么类型。任何符合这份契约的方法,都可以被委托实例持有、传递、随时调用。把行为当数据来对待,这正是函数式编程思想悄悄渗入 C# 的地方。


🧭 三个核心特性,记住就够了

委托的魅力集中在三点上。

类型安全。 绑定方法时,编译器会检查签名是否匹配,不符合就直接报错,彻底杜绝了 C 函数指针那种"运行时才爆炸"的惊喜。

一等公民。 委托实例可以赋值给变量、作为参数传入函数、作为返回值带出来------方法,从此可以像整数、字符串一样自由流动。

多播能力。 一个委托实例可以用 += 挂载多个方法,调用时按顺序逐一执行;用 -= 可以随时摘掉某一个。这是 .NET 事件机制的底层基础。


🔍 委托的三个演化层次

.NET 中的委托并非一成不变,它经历了从"原始声明"到"内置泛型"再到"Lambda 语法糖"的演化过程。理解这个层次,能让你读懂各个年代的代码,也能在合适的场景选对写法。

层次 语法形式 特点
原始委托 delegate 关键字声明类型 最基础,显式声明类型
泛型委托 Func<> / Action<> / Predicate<> BCL 内置,无需自定义类型
Lambda 表达式 x => x + 1 最简洁,现代 C# 主流写法

三者本质完全相同,Lambda 只是委托的语法糖,编译器最终都会将其转化为委托实例。


💻 代码说话

原始委托:从零开始绑定方法

csharp 复制代码
// 声明委托类型------定义"签名契约"
delegate int MathOperation(int a, int b);

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

    static void Main()
    {
        MathOperation op = Add;
        Console.WriteLine(op(3, 4));   // 输出: 7

        // 随时换绑另一个方法
        op = Multiply;
        Console.WriteLine(op(3, 4));   // 输出: 12
    }
}

多播委托:一次调用,多方响应

csharp 复制代码
delegate void Logger(string message);

class Program
{
    static void LogToConsole(string msg) => Console.WriteLine($"[Console] {msg}");
    static void LogToFile(string msg)    => Console.WriteLine($"[File]    {msg}");
    static void LogToCloud(string msg)   => Console.WriteLine($"[Cloud]   {msg}");

    static void Main()
    {
        Logger logger = LogToConsole;
        logger += LogToFile;
        logger += LogToCloud;

        logger("系统启动");
        // [Console] 系统启动
        // [File]    系统启动
        // [Cloud]   系统启动

        logger -= LogToFile;   // 摘掉文件日志
        logger("第二条日志");
        // [Console] 第二条日志
        // [Cloud]   第二条日志
    }
}

内置泛型委托:告别重复声明

.NET BCL 提供了三个开箱即用的泛型委托,覆盖了绝大多数场景,日常开发几乎不需要再手动 delegate 声明类型。

csharp 复制代码
// Func<TInput, TOutput> --- 有返回值
Func<int, int, int> add = (a, b) => a + b;
Console.WriteLine(add(5, 3));   // 8

// Action<T> --- 无返回值(void)
Action<string> greet = name => Console.WriteLine($"Hello, {name}!");
greet("World");                 // Hello, World!

// Predicate<T> --- 返回 bool,专为过滤而生
Predicate<int> isEven = n => n % 2 == 0;
Console.WriteLine(isEven(4));   // True
Console.WriteLine(isEven(7));   // False

委托作为参数:策略模式的精髓

这是委托最让人着迷的用法------把行为本身作为参数传进去,同一套逻辑框架,注入不同的策略,产生截然不同的结果。解耦做到这个程度,代码的弹性会大幅提升。

csharp 复制代码
class DataProcessor
{
    public static List<int> Filter(List<int> data, Predicate<int> condition)
        => data.Where(x => condition(x)).ToList();

    public static List<T> Transform<T>(List<int> data, Func<int, T> converter)
        => data.Select(converter).ToList();
}

class Program
{
    static void Main()
    {
        var numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

        var evens   = DataProcessor.Filter(numbers, n => n % 2 == 0);
        var bigOnes = DataProcessor.Filter(numbers, n => n > 6);
        var squares = DataProcessor.Transform(numbers, n => n * n);

        Console.WriteLine(string.Join(", ", evens));    // 2, 4, 6, 8, 10
        Console.WriteLine(string.Join(", ", bigOnes));  // 7, 8, 9, 10
        Console.WriteLine(string.Join(", ", squares));  // 1, 4, 9, 16, 25, 36, 49, 64, 81, 100
    }
}

事件:委托的"受保护封装"

event 关键字是对委托的进一步约束。外部代码只能 += 订阅或 -= 退订,无法直接赋值覆盖、也无法从外部触发调用------这道护栏,让发布-订阅模式变得既灵活又安全。

csharp 复制代码
class Button
{
    public event Action<string> Clicked;

    public void SimulateClick()
    {
        Console.WriteLine("按钮被点击...");
        Clicked?.Invoke("ButtonA");   // ?. 防止没有订阅者时的空引用
    }
}

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

        btn.Clicked += name => Console.WriteLine($"处理器1: {name} 被点击");
        btn.Clicked += name => Console.WriteLine($"处理器2: 记录日志 - {name}");

        btn.SimulateClick();
        // 按钮被点击...
        // 处理器1: ButtonA 被点击
        // 处理器2: 记录日志 - ButtonA
    }
}

💡 一张表收尾

概念 一句话理解
委托类型 方法签名的"合同书",规定参数与返回值
委托实例 持有具体方法引用的变量
多播委托 一条链上绑定多个方法,顺序执行
Func / Action 内置泛型委托,告别重复声明
Lambda 委托的匿名内联写法,现代 C# 的主流姿势
event 委托的受保护封装,专为发布-订阅模式设计

委托的核心哲学只有一句话:把行为当数据来传递。一旦建立起这个直觉,你会发现 LINQ、异步回调、依赖注入背后,都藏着同一个影子。

相关推荐
绛洞花主敏明1 小时前
Go操作xorm中间表多对多关联实战
开发语言·后端·golang
长栎1 小时前
手写一个表达式计算器,你就理解解释器模式了
后端
长栎1 小时前
foreach 语法糖背后,迭代器模式做了多少脏活
后端
HLAIA光子1 小时前
LLM缓存机制:你的API账单可以砍掉75%
后端·llm·ai编程
卷无止境1 小时前
统计质量控制(SQC / SPC):用数据说话的质量哲学
后端
XovH1 小时前
第 44篇 k8s之实战:将 Web 应用迁移到 Kubernetes(上)
后端
晓杰'1 小时前
从0到1实现Balatro游戏后端(7):Boss Blind与特殊规则实现
后端·websocket·typescript·node.js·游戏开发·项目实战·nestjs
MariaH1 小时前
Node.js 架构理解
后端
我登哥MVP1 小时前
Spring Boot 从“会用”到“精通”:请求映射原理
java·spring boot·后端·spring·servlet·maven·intellij-idea