在C#的委托体系中,Func和Action是微软封装好的两类泛型委托,无需开发者手动声明委托类型就能直接实现方法引用、回调传递等功能,大幅降低了委托使用的门槛。两者都支持最多16个输入参数,核心差异在于是否有返回值,是日常开发中处理委托场景的首选工具。

Func和Action的基础定义
Action是无返回值的泛型委托,所有重载形式的最后一个参数都不是输出参数,仅用于传递输入参数。Func是有返回值的泛型委托,最后一个泛型参数固定为返回值类型,前面的参数为输入参数。
Action的基础声明形式
Action的基础无参形式为Action,带1个参数的形式为Action<T>,带2个参数的形式为Action<T1,T2>,以此类推最多支持16个输入参数。
Func的基础声明形式
Func的基础带1个参数的形式为Func<TResult>(无输入参数,返回TResult类型),带1个输入参数和返回值的形式为Func<T,TResult>,同样最多支持16个输入参数,最后一个泛型参数为返回值类型。
基础用法示例
Action的使用示例
下面演示无参、1个参数、2个参数的Action使用场景:
using System;
class Program
{
static void Main()
{
// 无参Action
Action printHello = () => Console.WriteLine("Hello World");
printHello();
// 1个参数的Action
Action<string> printMsg = msg => Console.WriteLine(msg);
printMsg("这是单参数Action示例");
// 2个参数的Action
Action<string, int> printRepeat = (msg, count) =>
{
for (int i = 0; i < count; i++)
{
Console.WriteLine(msg);
}
};
printRepeat("重复输出", 3);
}
}
Func的使用示例
下面演示无输入参数、1个输入参数、2个输入参数的Func使用场景:
using System;
class Program
{
static void Main()
{
// 无输入参数,返回int的Func
Func<int> getRandom = () => new Random().Next(1, 100);
int randomNum = getRandom();
Console.WriteLine($"随机数:{randomNum}");
// 1个输入参数,返回string的Func
Func<int, string> numToStr = num => $"数字转换为字符串:{num}";
string strResult = numToStr(123);
Console.WriteLine(strResult);
// 2个输入参数,返回int的Func
Func<int, int, int> add = (a, b) => a + b;
int sum = add(10, 20);
Console.WriteLine($"10加20的结果是:{sum}");
}
}
泛型委托封装技巧
在实际开发中,我们可以基于Func和Action封装通用的处理逻辑,减少重复代码,以下是两个常见的封装场景。
封装通用重试逻辑
很多操作需要失败重试,我们可以封装一个通用的重试方法,通过Action传递需要执行的操作:
using System;
using System.Threading;
class RetryHelper
{
// 封装重试逻辑,action为要执行的操作,retryCount为重试次数,interval为重试间隔毫秒数
public static void Retry(Action action, int retryCount = 3, int interval = 1000)
{
int currentRetry = 0;
while (currentRetry < retryCount)
{
try
{
action();
// 执行成功则直接返回
return;
}
catch (Exception ex)
{
currentRetry++;
if (currentRetry >= retryCount)
{
throw new Exception($"重试{retryCount}次后仍然失败", ex);
}
Thread.Sleep(interval);
}
}
}
}
class Program
{
static void Main()
{
// 使用封装的重试方法执行可能失败的操作
Retry(() =>
{
// 模拟可能失败的操作
if (new Random().Next(0, 2) == 0)
{
throw new Exception("操作失败");
}
Console.WriteLine("操作执行成功");
}, 2, 500);
}
}
封装通用数据处理逻辑
对于需要返回结果的数据处理,可以用Func封装通用逻辑,比如封装一个带日志的数据处理方法:
using System;
class DataProcessHelper
{
// 封装带日志的数据处理,func为处理逻辑,operationName为操作名称
public static T ProcessWithLog<T>(Func<T> func, string operationName)
{
Console.WriteLine($"开始执行操作:{operationName}");
DateTime startTime = DateTime.Now;
try
{
T result = func();
TimeSpan costTime = DateTime.Now - startTime;
Console.WriteLine($"操作{operationName}执行完成,耗时:{costTime.TotalMilliseconds}毫秒");
return result;
}
catch (Exception ex)
{
Console.WriteLine($"操作{operationName}执行失败,错误信息:{ex.Message}");
throw;
}
}
}
class Program
{
static void Main()
{
// 使用封装的方法处理数据
int result = DataProcessHelper.ProcessWithLog(() =>
{
// 模拟数据处理逻辑
int sum = 0;
for (int i = 1; i <= 100; i++)
{
sum += i;
}
return sum;
}, "计算1到100的和");
Console.WriteLine($"计算结果:{result}");
}
}
使用注意事项
- Action和Func都是引用类型,使用时需要注意避免空引用异常,调用前可以先判断是否为null。
- 当委托逻辑比较简单时优先使用Lambda表达式赋值,逻辑复杂时可以指向已定义的方法,提升代码可读性。
- 如果需要超过16个输入参数,或者有特殊的需求(比如ref、out参数),还是需要自定义委托类型,Func和Action无法满足这类场景。
- 在异步场景中,可以使用
Func<Task>、Func<Task<T>>等配合异步方法使用,实现异步委托的传递。