Action和Func

1. 为什么需要 ActionFunc

在 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 框架供了一系列预定义好的、通用的委托类型,ActionFunc 就是其中最核心的两个。它们让我们无需定义自定义委托,直接使用即可,极大地提高了开发效率和代码的可读性。

2. 核心概念:什么是委托?

在深入 ActionFunc 之前,必须理解委托

  • 定义 :委托是一种类型安全的函数指针。它存储对一个或多个方法的引用。
  • 本质:委托是一个类,它定义了方法的签名(返回类型和参数列表)。任何与该签名匹配的方法都可以被赋值给这个委托变量。
  • 作用
    1. 将方法作为参数传递:实现高阶函数和回调机制。
    2. 事件处理:事件的底层就是基于委托实现的。
    3. 定义回调方法:例如,在异步操作完成时执行某个方法。

ActionFunc 就是 .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 中无处不在,例如 WhereSelectOrderBy 等方法都大量使用 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 表达式

ActionFunc 与 Lambda 表达式是天作之合。Lambda 提供了一种简洁、内联的方式来创建匿名方法,直接赋值给 ActionFunc 变量。

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 的查询方法高度依赖 FuncAction

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);
异步编程

在异步编程中,ActionFunc 也扮演着重要角色,尤其是在回调模式中。

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# 编程的基石
相关推荐
皮卡龙3 小时前
Java常用的JSON
java·开发语言·spring boot·json
工程师0073 小时前
TPL如何自动调整执行效率
c#·tpl
火山灿火山3 小时前
Qt常用控件(三)
开发语言·qt
利刃大大4 小时前
【JavaSE】十三、枚举类Enum && Lambda表达式 && 列表排序常见写法
java·开发语言·枚举·lambda·排序
float_六七4 小时前
Java反射:万能遥控器拆解编程
java·开发语言
han_hanker4 小时前
java 异常类——详解
java·开发语言
源码获取_wx:Fegn08954 小时前
基于springboot + vue健身房管理系统
java·开发语言·前端·vue.js·spring boot·后端·spring
LinHenrY12274 小时前
初识C语言(自定义结构:结构体)
c语言·开发语言
Matlab仿真实验室4 小时前
基于Matlab实现可见光通信仿真
开发语言·matlab