当前位置: 首页 > news >正文

Delegate、Action 与 Func 委托的全面解析

C# 编程中委托(Delegate)是一种强大的功能,它允许将方法作为参数传递、存储为变量或用于事件处理。delegate、Action 和 Func 是 C# 中处理委托的三种主要方式,它们极大地简化了委托的使用,提升了代码的可读性和可维护性。

一、委托基础概念

1. 委托的本质与作用

委托是一种类型安全的函数指针,它定义了方法的签名(参数和返回值类型),允许将方法作为参数传递或赋值给变量。可以将委托看作成一个存放方法的容器,需要用到的时候可以调用容器中的方法。

委托的主要作用包括:

  • ​方法传递​​:将方法作为参数传递给其他方法
  • ​回调机制​​:实现回调功能
  • ​事件处理​​:作为事件的基础机制
  • ​解耦​​:分离调用方和被调用方

2. 委托的类型

C# 中的委托主要分为三种:

  1. ​自定义 delegate​​:使用 delegate 关键字定义
  2. ​Action 委托​​:系统预定义的无返回值泛型委托
  3. ​Func 委托​​:系统预定义的有返回值泛型委托

二、自定义 delegate 详解

1. 基本定义与声明

自定义 delegate 是使用 delegate 关键字声明的委托类型,可以定义特定的方法签名。

// 声明一个无参数无返回值的委托
public delegate void MyDelegate();// 声明一个有参数有返回值的委托
public delegate int MethodDelegate(int x, int y);

自定义 delegate 至少 0 个参数,至多 32 个参数,可以无返回值,也可以指定返回值类型。

2. 使用示例

public class ExerciseDelagte
{public delegate void MyDelegate(); // 声明一个无参数无返回值的委托public MyDelegate myDelegate; // 初始化委托public void DelegateExample(){Console.Write("我是Delegate委托赋予的方法\n");}public void DelegateExample2(){Console.Write("我是Delegate多播委托的实例\n");}
}public class Demo
{static void Main(string[] args){ExerciseDelagte exdelegate = new ExerciseDelagte();exdelegate.myDelegate = exdelegate.DelegateExample; // 绑定实例方法exdelegate.myDelegate += exdelegate.DelegateExample2; // 可以绑定多个方法,称为多播委托exdelegate.myDelegate(); // 调用委托}
}

3. 多播委托

自定义 delegate 支持多播,即一个委托实例可以引用多个方法,使用 += 运算符添加方法,使用 -= 运算符移除方法。

exdelegate.myDelegate += exdelegate.DelegateExample2; // 添加方法
exdelegate.myDelegate -= exdelegate.DelegateExample2; // 移除方法

4. 使用场景

自定义 delegate 适用于以下情况:

  • 需要特定方法签名,而内置委托类型不满足需求时
  • 需要更明确的类型名提高代码可读性时
  • 需要支持多播委托时(虽然 Action 也可以实现,但不推荐)

三、Action 委托详解

1. Action 委托的基本概念

Action 委托是 C# 中表示无返回值方法的预定义委托类型,位于 System 命名空间下。它的核心特点是​​不返回任何值​​(void),但可以接受零到十六个输入参数。

Action 委托的主要形式包括:

  • Action:无参数
  • Action<T>:一个参数
  • Action<T1, T2>:两个参数
  • ...
  • Action<T1, ..., T16>:最多十六个参数

2. Action 委托的使用方式

无参数的 Action
Action greet = () => Console.WriteLine("Hello, World!");
greet(); // 输出: Hello, World!

这种形式常用于简单的无参数操作,如初始化或通知。

带参数的 Action
// 单参数 Action
Action<string> printMessage = (message) => Console.WriteLine(message);
printMessage("Hello, C#!"); // 输出: Hello, C#!// 多参数 Action
Action<int, string> printNumberAndMessage = (number, message) => 
{Console.WriteLine($"Number: {number}, Message: {message}");
};
printNumberAndMessage(42, "The answer"); // 输出: Number: 42, Message: The answer

带参数的 Action 适用于需要根据输入执行操作但不返回结果的场景。

3. Action 委托的实际应用

事件处理

Action 委托非常适合用于简单的事件处理机制。

public class Button
{public Action Click { get; set; }public void OnClick(){Click?.Invoke(); // 安全调用}
}// 使用示例
Button button = new Button();
button.Click = () => Console.WriteLine("Button clicked!");
button.OnClick(); // 输出: Button clicked!
回调函数

常见于异步操作完成后通知调用方。

void ProcessData(string data, Action<string> callback)
{Console.WriteLine($"Processing: {data}");callback("Process completed");
}// 使用示例
ProcessData("Sample data", result => Console.WriteLine($"Callback: {result}"));
/* 输出:
Processing: Sample data
Callback: Process completed
*/

四、Func 委托详解

1. Func 委托的基本概念

Func 委托是 C# 中表示有返回值方法的预定义委托类型,与 Action 的关键区别在于​​必须有一个返回值​​。Func 委托的最后一个类型参数总是返回值类型,前面是输入参数类型(0-16个)。

Func 委托的主要形式:

  • Func<TResult>:无参数,返回 TResult
  • Func<T, TResult>:一个参数,返回 TResult
  • Func<T1, T2, TResult>:两个参数,返回 TResult
  • ...
  • Func<T1, ..., T16, TResult>:最多十六个参数,返回 TResult

2. Func 委托的使用方式

无参数 Func
Func<string> getGreeting = () => "Hello, World!";
Console.WriteLine(getGreeting()); // 输出: Hello, World!
带参数 Func
// 单参数 Func
Func<int, string> intToString = num => num.ToString();
Console.WriteLine(intToString(42)); // 输出: 42// 多参数 Func
Func<int, int, int> add = (x, y) => x + y;
Console.WriteLine(add(3, 5)); // 输出: 8

3. Func 委托的实际应用

LINQ 查询

Func 委托是 LINQ 查询的核心组成部分,用于定义查询逻辑。

var numbers = new[] { 1, 2, 3, 4, 5 };// 使用Func过滤偶数
var evenNumbers = numbers.Where(n => n % 2 == 0);// 使用Func选择平方
var squares = numbers.Select(n => n * n);Console.WriteLine(string.Join(", ", evenNumbers)); // 输出: 2, 4
Console.WriteLine(string.Join(", ", squares)); // 输出: 1, 4, 9, 16, 25
策略模式

允许在运行时动态改变算法行为。

public class Calculator
{public double Calculate(double x, double y, Func<double, double, double> operation){return operation(x, y);}
}// 使用示例
var calc = new Calculator();
Console.WriteLine(calc.Calculate(5, 3, (a, b) => a + b)); // 输出: 8
Console.WriteLine(calc.Calculate(5, 3, (a, b) => a * b)); // 输出: 15

五、Delegate、Action 与 Func 委托的比较

1. 核心区别对比

特性自定义 delegateAction 委托Func 委托
​返回值​可自定义无 (void)有 (TResult)
​参数数量​0-32 个0-16 个0-16 个输入 + 1 个返回值
​多播支持​支持技术上可行但不推荐不适合
​定义方式​需要显式声明 delegate 类型使用系统预定义 Action使用系统预定义 Func
​典型用途​特定签名需求、多播执行操作、回调计算、转换、查询

2. 选择指南

  • ​​​使用自定义 delegate 当​​:

    • 需要特定的方法签名,而内置委托类型不满足
    • 需要多播委托功能
    • 需要更明确的类型名提高代码可读性
  • ​使用 Action 当​​:

    • 方法不需要返回值
    • 只需要执行某些操作(如打印、保存、通知)
    • 实现简单的事件处理或回调机制
  • ​使用 Func 当​​:

    • 方法需要返回值
    • 需要进行计算或转换
    • 实现策略模式或LINQ查询
    • 需要将方法作为参数传递并获取结果

六、用法与实践

1. 委托与事件

事件是基于委托的,但提供了更好的封装性和安全性:

public class ExerciseDelagte
{public delegate void MyDelegate();public event MyDelegate myEventDelegate; // 基于委托的事件public void CallEvent(){myEventDelegate?.Invoke(); // 安全调用}
}// 使用示例
ExerciseDelagte ex = new ExerciseDelagte();
ex.myEventDelegate += () => Console.WriteLine("Event triggered"); // 只能用+=或-=
ex.CallEvent(); // 输出: Event triggered

事件与普通委托的区别:

  • 事件只能在声明它的类内部触发
  • 外部只能用 += 和 -= 操作
  • 提供了更好的封装性

2. 异步编程

Func 委托可以很好地与异步编程结合:

public static async Task<TResult> ExecuteAsync<TResult>(Func<Task<TResult>> asyncFunc)
{return await asyncFunc();
}// 使用示例
Func<Task<int>> getRandomNumberAsync = async () => 
{await Task.Delay(1000);return new Random().Next(1, 100);
};
int number = await ExecuteAsync(getRandomNumberAsync);
Console.WriteLine($"Random number: {number}");

这种模式常用于抽象异步操作。

3. 性能考虑

虽然委托非常灵活,但在性能关键场景需要注意:

  • 委托调用比直接方法调用稍慢
  • 频繁创建新委托实例会产生GC压力
  • 对于高性能场景,考虑使用接口或具体类替代。

4. 类型推断与简写

C# 支持类型推断,可以简化委托的创建:

// 完整写法
Func<int, int> square = new Func<int, int>(x => x * x);// 简化写法
Func<int, int> square = x => x * x;

七、实际应用示例

1. 数据验证

public static bool ValidateData<T>(T data, Func<T, bool> validator)
{return validator(data);
}// 使用示例
Func<string, bool> isLongEnough = s => s.Length >= 8;
Console.WriteLine(ValidateData("password", isLongEnough)); // 输出: True
Console.WriteLine(ValidateData("short", isLongEnough));   // 输出: False

2. 处理管道

多个 Func 可以组合形成处理管道:

Func<string, string> toUpper = s => s.ToUpper();
Func<string, string> addExclamation = s => s + "!";
Func<string, string> process = s => addExclamation(toUpper(s));Console.WriteLine(process("hello")); // 输出: HELLO!

3. 工厂模式

public class ObjectFactory<T> where T : new()
{public Func<T> Create { get; set; } = () => new T();
}// 使用示例
var factory = new ObjectFactory<string>();
Console.WriteLine(factory.Create()); // 输出: (空字符串)

4. 窗体间通信

使用委托实现窗体间的值传递

// Form1代码
public partial class Form1 : Form
{public Form1(){InitializeComponent();}private void button1_Click(object sender, EventArgs e){Form2 form2 = new Form2(DateTime.Now);form2.dataReturned += Form2DataReturn; // 事件绑定form2.Show();}private void Form2DataReturn(string obj){this.Text = obj; // 接收Form2返回的数据}
}// Form2代码
public partial class Form2 : Form
{private DateTime dateTime;public event Action<string> dataReturned; // 定义事件public Form2(DateTime now){InitializeComponent();this.dateTime = now;this.FormClosed += Form2_FormClosed; // 绑定关闭事件}private void Form2_FormClosed(object sender, FormClosedEventArgs e){dataReturned?.Invoke(dateTime.ToString("yyyy-MM-dd")); // 触发事件}
}

六、总结

Action 和 Func 委托是 C# 中极其强大的工具

  1. ​简化代码​​:减少自定义委托类型的需要,使代码更简洁
  2. ​提高可读性​​:通过明确的命名表达意图
  3. ​增强灵活性​​:支持将方法作为参数传递,实现策略模式等
  4. ​与Lambda完美结合​​:支持使用简洁的Lambda表达式定义行为
  5. ​是LINQ的基础​​:Func委托是LINQ查询的核心组件

参考:

  1. C#知识|系统泛型委托Func和Action
  2. Func与Action的区别?
  3. C#语言中的 Action 和 Func 委托使用详解
  4. C#之Func委托
  5. C#中 Action 委托的使用与常见问题解析
  6. C# 中的 Action 委托详解
  7. C#中的Action委托
  8. C# 委托Delegate、Action、Func和Event的区别
  9. C#委托的介绍(delegate、Action、Func、predicate)
http://www.dtcms.com/a/295225.html

相关文章:

  • GitHub Actions打包容器,推送 AWS ECR 并使 EKS 自动拉取以完成发版部署
  • 【Java基础06】ArrayList
  • 软考 系统架构设计师系列知识点之杂项集萃(115)
  • Python 程序设计讲义(14):Python 的数据运算——数值运算
  • RabbitMQ--消息顺序性
  • Java集合去重
  • OpenMed 项目深度分析:推动医疗 NLP 领域的开源革命
  • pcie常用的查看寄存器方法
  • node.js中的path模块
  • 低速信号设计之 QSPI 篇
  • 【LeetCode数据结构】二叉树的应用(一)——单值二叉树问题、相同的树问题、对称二叉树问题、另一棵树的子树问题详解
  • Faiss中L2欧式距离与余弦相似度:究竟该如何选择?
  • Web前端入门:JavaScript 哪些地方需要 try...catch 异常捕获
  • 【图论】倍增与lca
  • Avalonia 基于MVVM的时间统计/系统时间显示 示例
  • EPSON爱普生全系列废墨垫已满清零工具分享附教程下载
  • EasyExcel 模板导出数据 + 自定义策略(合并单元格)
  • 基于深度学习的胸部 X 光图像肺炎分类系统(三)
  • Turbo Intruder 并发插件无法试用--更换新版Burpsuit解决(简单解决安装、破解问题)
  • 开源Qwen凌晨暴击闭源Claude!刷新AI编程SOTA,支持1M上下文
  • 跨境支付入门~国际支付结算(结算篇)
  • AtCoder Beginner Contest 415(ABCDE)
  • `neutron router-gateway-set` 操作失败的可能原因及解决方案
  • 深度分析Java多线程机制
  • 【智能协同云图库】智能协同云图库第六弹:空间模块开发
  • 微服务的编程测评系统6-管理员登录前端-前端路由优化
  • 【开源】WPF的数据可视化大屏解决方案——WpfMap
  • 洛谷 P11378 [GESP202412 七级] 燃烧-普及/提高-
  • fdbus4.2 timer的使用
  • AI时代,我的编程工作搭子