C delegate如何作为参数传递?

99ANYc3cd6
预计阅读时长 21 分钟
位置: 首页 参数 正文

为什么需要将委托作为参数?

将委托作为参数,本质上是将“行为”或“方法”作为参数传递给另一个方法,这允许接收方方法(被调用者)在执行过程中,可以调用由调用方传入的这个“行为”。

c delegate 作为参数
(图片来源网络,侵删)

这带来了几个关键好处:

  • 回调:一个方法可以告诉另一个方法:“在你完成某个任务后,请调用这个方法(委托)来通知我结果或状态。”
  • 代码复用与灵活性:你可以编写一个通用的算法或框架,而将具体的实现细节(比如如何比较两个对象、如何处理一个元素)留给调用者通过委托来定义。
  • 事件处理:事件模型完全建立在委托的基础上,你订阅一个事件,本质上就是将一个方法(事件处理器)作为参数注册到事件源上。

实现步骤

将委托作为参数传递,通常遵循以下步骤:

  1. 定义委托类型:你需要声明一个委托类型,这个委托类型定义了它所能封装的方法的“签名”(返回类型和参数列表)。
  2. 创建委托实例:创建一个委托实例,并将其绑定到一个与委托签名匹配的具体方法上。
  3. 调用方法并传递委托:在一个方法调用中,将这个委托实例作为参数传递给另一个方法。
  4. 在目标方法内部执行委托:接收参数的方法内部,可以像调用普通方法一样调用传入的委托。

代码示例

下面我们通过几个从简单到复杂的例子来理解这个过程。

示例 1:最基础的回调

这个例子将展示一个经典的异步回调场景。

c delegate 作为参数
(图片来源网络,侵删)

步骤 1 & 2: 定义委托并创建实例

我们定义一个名为 TaskCompleteHandler 的委托,它接受一个 string 类型的消息作为参数,没有返回值,我们创建一个与它签名匹配的方法 OnTaskDone,并将其赋值给委托实例。

using System;
using System.Threading; // 为了演示耗时操作
// 1. 定义委托类型
// 它可以封装任何返回 void, 并接受一个 string 参数的方法。
public delegate void TaskCompleteHandler(string message);
public class Program
{
    // 2. 一个与 TaskCompleteHandler 签名匹配的方法
    public static void OnTaskDone(string result)
    {
        Console.WriteLine($"收到回调通知!任务已完成,结果是: {result}");
    }
    public static void Main(string[] args)
    {
        // 创建委托实例,并将其绑定到 OnTaskDone 方法
        TaskCompleteHandler callback = OnTaskDone;
        // 3. 调用另一个方法,并将委托作为参数传递
        StartLongRunningProcess(callback);
        Console.WriteLine("主线程继续执行其他工作...");
        Console.ReadLine(); // 防止控制台窗口立即关闭
    }
    // 4. 这个方法接受一个 TaskCompleteHandler 类型的参数
    public static void StartLongRunningProcess(TaskCompleteHandler handler)
    {
        Console.WriteLine("耗时任务开始...");
        // 模拟一个耗时操作(比如网络请求、数据库查询)
        Thread.Sleep(2000); 
        Console.WriteLine("耗时任务结束,准备通知调用者...");
        // 在任务完成后,调用传入的委托(回调)
        handler?.Invoke("数据处理成功!");
    }
}

输出结果:

耗时任务开始...
主线程继续执行其他工作...
耗时任务结束,准备通知调用者...
收到回调通知!任务已完成,结果是: 数据处理成功!

分析:

c delegate 作为参数
(图片来源网络,侵删)
  • StartLongRunningProcess 方法本身不关心任务完成后具体要做什么,它只负责在完成时调用 handler 这个委托。
  • 调用者(Main 方法)通过传入 OnTaskDone 方法,定义了任务完成后的具体行为,这就是解耦和灵活性的体现。

示例 2:使用 ActionFunc (现代 C# 推荐)

在 C# 2.0 之后,微软引入了几个内置的泛型委托类型,如 ActionFunc,我们通常不需要自己定义委托类型,可以直接使用它们,使代码更简洁。

  • Action:用于封装没有返回值的方法。Action<T> 表示接受一个 T 类型参数,返回 void 的方法。
  • Func:用于封装有返回值的方法。Func<T, TResult> 表示接受一个 T 类型参数,返回 TResult 类型的方法。

使用 Action 的例子:

我们将上面的例子改写,使用 Action<string> 代替自定义的 TaskCompleteHandler

using System;
using System.Threading;
public class Program
{
    // 这个方法签名与 Action<string> 完全匹配
    public static void OnTaskDone(string result)
    {
        Console.WriteLine($"收到回调通知!任务已完成,结果是: {result}");
    }
    public static void Main(string[] args)
    {
        // 直接使用 Action<string>,无需定义委托类型
        Action<string> callback = OnTaskDone;
        // 调用时,传递 Action 委托
        StartLongRunningProcess(callback);
        Console.WriteLine("主线程继续执行其他工作...");
        Console.ReadLine();
    }
    // 方法签名改为接受 Action<string>
    public static void StartLongRunningProcess(Action<string> handler)
    {
        Console.WriteLine("耗时任务开始...");
        Thread.Sleep(2000);
        Console.WriteLine("耗时任务结束,准备通知调用者...");
        // 调用方式不变
        handler?.Invoke("数据处理成功!");
    }
}

输出结果与之前完全相同,可以看到,代码更简洁了。


示例 3:作为“策略”传递(更高级的用法)

假设我们有一个方法,需要对一个整数列表进行某种操作,但具体是什么操作(比如求和、求最大值、打印所有值)由调用者决定。

using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
    public static void Main(string[] args)
    {
        List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
        // 场景1:我们想对每个数字进行平方操作
        Console.WriteLine("对每个数字进行平方操作:");
        ProcessList(numbers, Square);
        Console.WriteLine(); // 换行
        // 场景2:我们想判断每个数字是否为偶数
        Console.WriteLine("判断每个数字是否为偶数:");
        ProcessList(numbers, IsEven);
        Console.WriteLine();
        // 场景3:使用 Lambda 表达式直接定义行为,更简洁
        Console.WriteLine("打印所有大于5的数字:");
        ProcessList(numbers, num => Console.WriteLine($"数字 {num} 大于5"));
    }
    // 定义委托类型,接受一个 int,返回 void
    public delegate void NumberProcessor(int number);
    // 核心方法:接受一个委托作为参数
    public static void ProcessList(List<int> list, NumberProcessor processor)
    {
        if (list == null || processor == null) return;
        foreach (var num in list)
        {
            // 对列表中的每个元素,调用传入的“处理器”
            processor(num);
        }
    }
    // 具体的处理方法1
    public static void Square(int num)
    {
        Console.WriteLine($"平方: {num} * {num} = {num * num}");
    }
    // 具体的处理方法2
    public static void IsEven(int num)
    {
        Console.WriteLine($"{num} 是 {(num % 2 == 0 ? "偶数" : "奇数")}");
    }
}

输出结果:

对每个数字进行平方操作:
平方: 1 * 1 = 1
平方: 2 * 2 = 4
平方: 3 * 3 = 9
平方: 4 * 4 = 16
平方: 5 * 5 = 25
平方: 6 * 6 = 36
平方: 7 * 7 = 49
平方: 8 * 8 = 64
平方: 9 * 9 = 81
平方: 10 * 10 = 100
判断每个数字是否为偶数:
1 是 奇数
2 是 偶数
3 是 奇数
4 是 偶数
5 是 奇数
6 是 偶数
7 是 奇数
8 是 偶数
9 是 奇数
10 是 偶数
打印所有大于5的数字:
数字 6 大于5
数字 7 大于5
数字 8 大于5
数字 9 大于5
数字 10 大于5

分析:

  • ProcessList 是一个通用的列表处理方法。
  • NumberProcessor 委托作为“策略”参数,传递了具体的处理逻辑。
  • 调用者可以通过传递不同的方法(如 Square, IsEven)或 Lambda 表达式(如 num => Console.WriteLine(...))来改变 ProcessList 的行为,而无需修改 ProcessList 的任何代码,这正是“开闭原则”(对扩展开放,对修改关闭)的体现。

特性 说明
核心思想 将方法作为参数传递,实现行为注入。
关键步骤 定义委托 (或使用 Action/Func)
2. 创建委托实例并绑定方法
3. 将委托作为参数传递
4. 在目标方法中调用委托。
主要用途 回调事件策略模式自定义排序/查找 (如 List<T>.SortComparison<T 参数)。
现代实践 优先使用 Action (无返回值) 和 Func (有返回值) 等泛型委托,它们更灵活且类型安全,Lambda 表达式是创建委托实例的绝佳方式,能让代码更简洁。

掌握将委托作为参数传递,是迈向 C# 高级编程的重要一步,它极大地增强了代码的灵活性、可读性和可维护性。

-- 展开阅读全文 --
头像
ThinkPad E560拆机教程,关键步骤有哪些?
« 上一篇 今天
summit智能手表有何独特功能?
下一篇 » 今天

相关文章

取消
微信二维码
支付宝二维码

最近发表

标签列表

目录[+]