C#的Action是怎么实现的
在 C# 中,Action 是一组预定义的泛型委托类型,用于表示“无返回值”的方法引用。它的核心实现依赖于 .NET 委托体系,本质是编译器/框架预定义的委托模板,而非复杂的自定义类型。下面从定义本质、源码实现、使用原理三个维度详细拆解:
一、Action 的核心定义:预定义泛型委托
Action 是 .NET Base Class Library (BCL) 中在 System 命名空间下预定义的委托,核心目的是简化“无返回值方法”的委托声明,避免开发者重复定义相同结构的委托。
1. 官方源码(简化版)
.NET 框架中 Action 的定义本质是一系列重载的泛型委托,源码(以 .NET 6+ 为例)位于 System.dll 的 System 命名空间下,核心结构如下:
// 无参数的 Action
namespace System
{
// [Serializable]
public delegate void Action();
}
// 1个参数的 Action<T>
namespace System
{
// [Serializable]
public delegate void Action<in T>(T obj);
}
// 2个参数的 Action<T1, T2>
namespace System
{
// [Serializable]
public delegate void Action<in T1, in T2>(T1 arg1, T2 arg2);
}
// ... 以此类推,直到 Action<T1, T2, ..., T16>(最多16个参数)
关键特性:
- 无返回值:所有
Action委托的返回类型都是void(区别于有返回值的Func)。 - 协变/逆变:泛型参数标注
in(逆变),符合委托参数的类型安全规则(比如可以用Action<object>接收Action<string>)。 - 参数数量:框架预定义了从 0 到 16 个参数的重载(
Action、Action<T1>、Action<T1,T2>...Action<T1..T16>),覆盖绝大多数场景。
二、Action 的底层实现原理
Action 本质是委托类型,而 C# 委托的底层依赖 CLR 的委托机制,核心分为两步:
1. 委托的 CLR 本质
所有委托(包括 Action)最终都会被编译为继承自 System.Delegate(基类)/ System.MulticastDelegate(多播委托)的密封类,自动生成以下核心成员:
Invoke()方法:与委托签名匹配(比如Action<int>的Invoke(int obj)),调用委托时实际执行此方法。BeginInvoke()/EndInvoke():异步调用的方法(.NET Framework 特有,.NET Core/.NET 5+ 已移除,改用 Task)。_target字段:存储委托绑定的实例(实例方法时)。_methodPtr字段:存储方法的指针(指向被绑定的方法)。
2. Action 的编译过程
当你使用 Action 时,编译器会:
- 识别
Action对应的泛型委托版本(比如Action<int, string>对应 2 参数的委托)。 - 将方法/lambda 表达式绑定到该委托的
Invoke方法。 - 最终生成符合 CLR 委托规范的 IL 代码。
示例:IL 层面的简化对比
// 代码1:手动定义委托
public delegate void MyAction(int a);
MyAction myAction = x => Console.WriteLine(x);
// 代码2:使用 Action
Action<int> action = x => Console.WriteLine(x);
上述两段代码编译后的 IL 几乎完全一致——Action<int> 只是框架替你预定义了 MyAction 这个委托,无需手动声明。
三、Action 的使用场景与底层执行
1. 基础使用(绑定方法/匿名方法/lambda)
// 1. 绑定普通方法
void Print(string msg) => Console.WriteLine(msg);
Action<string> printAction = Print;
printAction("Hello"); // 执行:调用 Print 方法
// 2. 绑定 lambda 表达式(编译器自动转换为委托)
Action<int, int> sumAction = (a, b) => Console.WriteLine(a + b);
sumAction(1, 2); // 执行:调用 lambda 生成的匿名方法
// 3. 多播委托(Action 继承 MulticastDelegate,支持 +=)
Action multiAction = () => Console.WriteLine("A");
multiAction += () => Console.WriteLine("B");
multiAction(); // 执行:依次调用两个方法
2. 执行原理
调用 Action 实例时,本质是调用其 Invoke 方法:
- 若绑定的是实例方法:
_target指向实例对象,_methodPtr指向实例方法的地址,执行时通过实例调用方法。 - 若绑定的是静态方法:
_target为null,直接通过_methodPtr调用静态方法。 - 若绑定的是lambda/匿名方法:编译器会生成一个隐藏的静态/实例方法,再将
Action绑定到该方法。
四、Action 与 Func 的区别(补充)
| 特性 | Action | Func |
|---|---|---|
| 返回值 | 无(void) | 有(TResult) |
| 核心用途 | 执行操作(无返回) | 执行计算(有返回) |
| 泛型参数 | 仅输入参数(T1, T2...) | 最后一个是返回值(T1,T2,TResult) |
五、自定义扩展 Action(理解实现本质)
如果你想手动实现一个“类似 Action”的委托,本质就是定义无返回值的泛型委托:
// 自定义 1 参数的 Action 替代版
namespace MyNamespace
{
public delegate void MyAction<in T>(T param);
}
// 使用自定义委托(效果与 Action<T> 完全一致)
MyAction<string> myAction = msg => Console.WriteLine(msg);
myAction("Custom Action");
总结
Action 的实现核心是:
- 预定义委托:框架在
System命名空间下预定义了一系列无返回值的泛型委托(0~16 参数)。 - 委托底层:所有
Action最终继承MulticastDelegate,依赖 CLR 的委托机制(Invoke方法、_target/_methodPtr字段)。 - 编译器适配:lambda/匿名方法会被编译器自动转换为符合
Action签名的委托实例。
简单来说,Action 不是“特殊类型”,只是 .NET 为了简化开发提供的无返回值委托模板,其底层完全遵循 C# 委托的通用实现规则。