> 技术文档 > Delegate、Action 与 Func 委托的全面解析

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:一个参数
  • Action:两个参数
  • ...
  • Action:最多十六个参数

2. Action 委托的使用方式

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

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

带参数的 Action
// 单参数 ActionAction printMessage = (message) => Console.WriteLine(message);printMessage(\"Hello, C#!\"); // 输出: Hello, C#!// 多参数 ActionAction 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 callback){ Console.WriteLine($\"Processing: {data}\"); callback(\"Process completed\");}// 使用示例ProcessData(\"Sample data\", result => Console.WriteLine($\"Callback: {result}\"));/* 输出:Processing: Sample dataCallback: Process completed*/

四、Func 委托详解

1. Func 委托的基本概念

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

Func 委托的主要形式:

  • Func:无参数,返回 TResult
  • Func:一个参数,返回 TResult
  • Func:两个参数,返回 TResult
  • ...
  • Func:最多十六个参数,返回 TResult

2. Func 委托的使用方式

无参数 Func
Func getGreeting = () => \"Hello, World!\";Console.WriteLine(getGreeting()); // 输出: Hello, World!
带参数 Func
// 单参数 FuncFunc intToString = num => num.ToString();Console.WriteLine(intToString(42)); // 输出: 42// 多参数 FuncFunc 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, 4Console.WriteLine(string.Join(\", \", squares)); // 输出: 1, 4, 9, 16, 25
策略模式

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

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

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

1. 核心区别对比

特性 自定义 delegate Action 委托 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 ExecuteAsync(Func<Task> asyncFunc){ return await asyncFunc();}// 使用示例Func<Task> 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 square = new Func(x => x * x);// 简化写法Func square = x => x * x;

七、实际应用示例

1. 数据验证

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

2. 处理管道

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

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

3. 工厂模式

public class ObjectFactory where T : new(){ public Func Create { get; set; } = () => new T();}// 使用示例var factory = new ObjectFactory();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 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)