1. 为什么需要 Action 和 Func?
在 C# 中,我们经常需要将方法作为参数传递给其他方法,或者将方法存储在变量中以便稍后调用。传统上,我们需要先定义一个与目标方法签名完全匹配的委托类型,这非常繁琐。
例如,如果我们想传递一个没有返回值、有两个 int 参数的方法,我们需要这样写:
cs
// 1. 自定义委托类型
public delegate void MyCustomDelegate(int a, int b);
// 2. 定义一个符合该签名的方法
public static void Add(int x, int y)
{
Console.WriteLine($"Sum is: {x + y}");
}
// 3. 使用委托
MyCustomDelegate del = Add;
del(10, 20);
这个过程很啰嗦。为了解决这一问题,.NET 框架供了一系列预定义好的、通用的委托类型,Action 和 Func 就是其中最核心的两个。它们让我们无需定义自定义委托,直接使用即可,极大地提高了开发效率和代码的可读性。
2. 核心概念:什么是委托?
在深入 Action 和 Func 之前,必须理解委托。
- 定义 :委托是一种类型安全的函数指针。它存储对一个或多个方法的引用。
- 本质:委托是一个类,它定义了方法的签名(返回类型和参数列表)。任何与该签名匹配的方法都可以被赋值给这个委托变量。
- 作用 :
- 将方法作为参数传递:实现高阶函数和回调机制。
- 事件处理:事件的底层就是基于委托实现的。
- 定义回调方法:例如,在异步操作完成时执行某个方法。
Action 和 Func 就是 .NET 为我们预先定义好的、最常用的两种委托"模板"。
3. Action 委托
定义与作用
Action 是一个委托,它封装一个没有返回值(即返回 void)的方法。
Action 是一个泛型委托,它有多个变体,可以接受 0 到 16 个不同类型的参数。
Action:封装一个无参数、无返回值的方法。
public delegate void Action();Action<T>:封装一个有一个参数、无返回值的方法。
public delegate void Action<in T>(T obj);Action<T1, T2>:封装一个有两个参数、无返回值的方法。
public delegate void Action<in T1, in T2>(T1 arg1, T2 arg2);- ...
Action<T1, T2, ..., T16>:封装有16个参数、无返回值的方法。
注意 :
in关键字表示逆变 。简单理解,可以用一个比声明时更"通用"的类型的对象来调用它。例如,Action<object>可以接受一个string类型的参数,因为string可以隐式转换为object。
原理
Action 的原理就是 .NET 框架定义好了的 public delegate void Action<...> 声明。当使用 Action<string> 时,编译器会自动将其解析为对 System.Action 泛型类型的引用。
各种使用方法与实例
实例 1:使用 Action 无参版本
cs
// 定义一个符合 Action 签名的方法
static void SayHello()
{
Console.WriteLine("Hello, World!");
}
// 创建 Action 委托并调用
Action actionDelegate = SayHello;
actionDelegate(); // 输出: Hello, World!
// 也可以直接使用 Lambda 表达式
Action anotherAction = () => Console.WriteLine("Hello from Lambda!");
anotherAction(); // 输出: Hello from Lambda!
实例 2:使用 Action<T> 带参版本
cs
// 定义一个符合 Action<string> 签名的方法
static void PrintMessage(string message)
{
Console.WriteLine($"Message: {message}");
}
// 创建 Action 委托
Action<string> printAction = PrintMessage;
printAction("This is a test."); // 输出: Message: This is a test.
// 使用 Lambda 表达式
Action<int> printNumber = num => Console.WriteLine($"The number is: {num}");
printNumber(42); // 输出: The number is: 42
实例 3:使用 Action<T1, T2> 多参版本
cs
// 定义一个符合 Action<string, int> 签名的方法
static void LogUserActivity(string username, int activityCount)
{
Console.WriteLine($"User '{username}' has performed {activityCount} activities.");
}
// 创建 Action 委托
Action<string, int> logAction = LogUserActivity;
logAction("Alice", 5); // 输出: User 'Alice' has performed 5 activities.
// 使用 Lambda 表达式
Action<string, bool> setStatus = (name, isActive) =>
{
Console.WriteLine($"Setting status for {name} to {(isActive ? "Active" : "Inactive")}).");
};
setStatus("Bob", true); // 输出: Setting status for Bob to Active).
实例 4:作为方法参数
这是 Action 最强大的用途之一,允许定义一个通用的处理逻辑,而具体的操作由调用者决定。
cs
// 这个方法接受一个 Action<string> 作为参数
// 它会对列表中的每个元素执行这个 Action
static void ProcessItems(List<string> items, Action<string> processor)
{
foreach (var item in items)
{
processor(item); // 调用传入的委托
}
}
// 调用 ProcessItems
var names = new List<string> { "Tom", "Jerry", "Spike" };
// 场景1:打印每个名字
ProcessItems(names, name => Console.WriteLine($"Processing name: {name}"));
// 场景2:将每个名字转换为大写
ProcessItems(names, name => Console.WriteLine(name.ToUpper()));
// 场景3:检查名字长度
ProcessItems(names, name => Console.WriteLine($"'{name}' has {name.Length} letters."));
4. Func 委托
定义与作用
Func 是一个委托,它封装一个有返回值的方法。
Func 也是一个泛型委托,但它与 Action 的关键区别在于它必须有一个返回类型参数 。Func 可以接受 0 到 16 个输入参数,但必须有且只有一个输出类型参数,并且这个输出类型参数必须放在最后。
Func<TResult>:封装一个无参数、有返回值的方法。
public delegate Func<out TResult>();Func<T, TResult>:封装一个有一个参数、有返回值的方法。
public delegate Func<in T, out TResult>(T arg);Func<T1, T2, TResult>:封装有两个参数、有返回值的方法。
public delegate Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2);- ...
Func<T1, T2, ..., T16, TResult>:封装有16个参数、有返回值的方法。
注意 :
out关键字表示协变 。简单理解,就是委托的返回类型可以比声明时更"具体"。例如,Func<string>可以赋值给一个Func<object>类型的变量,因为string可以隐式转换为object。
原理
与 Action 类似,Func 也是 .NET 框架预定义好的委托类型,如 public delegate Func<out TResult>()。它提供了一个通用的模板来引用任何有返回值的方法。
各种使用方法与实例
实例 1:使用 Func<TResult> 无参有返回值
cs
// 定义一个符合 Func<int> 签名的方法
static int GetCurrentYear()
{
return DateTime.Now.Year;
}
// 创建 Func 委托
Func<int> getYearFunc = GetCurrentYear;
int currentYear = getYearFunc();
Console.WriteLine($"Current year is: {currentYear}"); // 输出: Current year is: 2024
// 使用 Lambda 表达式
Func<string> getGreeting = () => "Welcome back!";
string greeting = getGreeting();
Console.WriteLine(greeting); // 输出: Welcome back!
实例 2:使用 Func<T, TResult> 带参有返回值
cs
// 定义一个符合 Func<string, int> 签名的方法
static int GetStringLength(string text)
{
return text.Length;
}
// 创建 Func 委托
Func<string, int> lengthFunc = GetStringLength;
int length = lengthFunc("Hello C#");
Console.WriteLine($"Length is: {length}"); // 输出: Length is: 7
// 使用 Lambda 表达式
Func<int, bool> isEven = x => x % 2 == 0;
Console.WriteLine($"Is 10 even? {isEven(10)}"); // 输出: Is 10 even? True
Console.WriteLine($"Is 7 even? {isEven(7)}"); // 输出: Is 7 even? False
实例 3:使用 Func<T1, T2, TResult> 多参有返回值
cs
// 定义一个符合 Func<double, double, double> 签名的方法
static double Multiply(double a, double b)
{
return a * b;
}
// 创建 Func 委托
Func<double, double, double> multiplyFunc = Multiply;
double result = multiplyFunc(5.5, 2.0);
Console.WriteLine($"Result: {result}"); // 输出: Result: 11
// 使用 Lambda 表达式
Func<string, string, string> concatenate = (s1, s2) => s1 + " " + s2;
string fullString = concatenate("Hello", "World");
Console.WriteLine(fullString); // 输出: Hello World
实例 4:作为方法参数(LINQ 的核心)
Func 在 LINQ 中无处不在,例如 Where、Select、OrderBy 等方法都大量使用 Func 作为参数。
cs
var numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
// Where 方法接受一个 Func<int, bool> 参数
// 这个 Func 决定了元素是否应该被包含在结果中
var evenNumbers = numbers.Where(n => n % 2 == 0);
Console.WriteLine("Even numbers: " + string.Join(", ", evenNumbers));
// 输出: Even numbers: 2, 4, 6, 8, 10
// Select 方法接受一个 Func<int, string> 参数
// 这个 Func 定义了如何将每个元素转换成另一种类型
var squaredStrings = numbers.Select(n => $"Square of {n} is {n * n}");
foreach (var s in squaredStrings)
{
Console.WriteLine(s);
}
// 输出:
// Square of 1 is 1
// Square of 2 is 4
// ...等等
5. Action vs. Func:核心区别
| 特性 | Action |
Func |
|---|---|---|
| 返回值 | 没有返回值 (返回 void) |
必须有返回值 |
| 泛型参数 | 全部都是输入参数 | 前 N 个是输入参数,最后一个 是输出(返回)类型 |
| 记忆口诀 | Act (行动),做了某件事,但没有结果。 | Function (函数),有输入,必然有输出。 |
如何选择?
- 如果要封装的方法只执行操作,不返回任何值 ,使用
Action。- 例如:
Log(message),SaveToFile(data),ShowMessageBox(text)。
- 例如:
- 如果要封装的方法需要根据输入计算并返回一个结果 ,使用
Func。- 例如:
CalculateSum(a, b),GetUserName(id),ParseToInt(input)。
- 例如:
6. 高级应用场景
Lambda 表达式
Action 和 Func 与 Lambda 表达式是天作之合。Lambda 提供了一种简洁、内联的方式来创建匿名方法,直接赋值给 Action 或 Func 变量。
cs
// Action 示例
Action<string> log = msg => Console.WriteLine($"[LOG] {DateTime.Now}: {msg}");
log("Application started.");
// Func 示例
Func<int, int, int> add = (x, y) => x + y;
Func<int, int, int> multiply = (x, y) => x * y;
int result1 = add(10, 20);
int result2 = multiply(10, 20);
LINQ 中的使用
如前所述,LINQ 的查询方法高度依赖 Func 和 Action。
cs
var people = new List<Person>
{
new Person { Name = "Alice", Age = 30 },
new Person { Name = "Bob", Age = 25 },
new Person { Name = "Charlie", Age = 35 }
};
// Func<Person, bool> 用于筛选
var adults = people.Where(p => p.Age >= 30);
// Func<Person, string> 用于投影
var names = people.Select(p => p.Name);
// Func<Person, Person, int> 用于排序
var sortedByName = people.OrderBy(p => p.Name);
异步编程
在异步编程中,Action 和 Func 也扮演着重要角色,尤其是在回调模式中。
cs
// 模拟一个异步下载操作
async Task DownloadDataAsync(string url, Action<string> onComplete)
{
await Task.Delay(2000); // 模拟网络延迟
string data = $"Data from {url}";
onComplete(data); // 下载完成后,调用回调方法
}
// 调用异步方法
Console.WriteLine("Starting download...");
DownloadDataAsync("https://example.com", data =>
{
Console.WriteLine("Download complete!");
Console.WriteLine($"Received: {data}");
});
Console.WriteLine("Download initiated, doing other work...");
事件处理
事件的 Invoke 方法在底层就是一个 Action。当订阅事件时,实际上就是在将一个方法(或 Lambda)赋值给这个 Action 委托链。
cs
public class Button
{
// 这是一个 Action 委托
public event EventHandler Clicked;
public void OnClick()
{
Clicked?.Invoke(this, EventArgs.Empty);
}
}
var button = new Button();
// 使用 Lambda 表达式订阅事件
button.Clicked += (sender, e) => Console.WriteLine("Button was clicked!");
button.OnClick(); // 输出: Button was clicked!
7. 总结
| 特性 | Action |
Func |
|---|---|---|
| 本质 | 预定义的、无返回值的泛型委托 | 预定义的、有返回值的泛型委托 |
| 核心作用 | 将"过程"或"操作"作为参数传递 | 将"计算"或"转换"作为参数传递 |
| 语法特点 | Action<T1, T2, ...> (无返回类型) |
Func<T1, T2, ..., TResult> (最后一个参数是返回类型) |
| 最佳实践 | 用于封装 void 方法,如日志、UI更新、文件保存等 | 用于封装有返回值的方法,如数据查询、数学计算、数据转换等 |
| 与 Lambda | 绝配,使代码简洁、匿名化 | 绝配,是 LINQ 和现代 C# 编程的基石 |