我知道如何在 .NET 中使用Action
和Func
,但每次我开始使用时,都可以使用我调用的常规旧方法来实现完全相同的解决方案。
这不包括当Action
orFunc
用作我无法控制的东西的参数时,比如 LINQ 的.Where
.
所以基本上我的问题是......为什么这些存在?他们给了我什么额外的和新的东西,而简单的方法却没有?
我认为这里的其他答案是关于Action
/Func
是什么及其用途。我将尝试回答如何在Action
/Func
和方法之间进行选择。先说区别:
1)从原始性能的角度来看,委托比直接方法调用要慢,但它是如此微不足道,以至于担心它是一种不好的做法。
2)方法可以具有重载(具有不同签名的相同函数名称),但不能具有Action
/Func
委托,因为它们被声明为变量,并且根据 C# 规则,您不能在给定范围内拥有两个具有相同名称的变量。
bool IsIt() { return 1 > 2; }
bool IsIt(int i) { return i > 2; } //legal
Func<bool> IsIt = () => 1 > 2;
Func<int, bool> IsIt = i => i > 2; //illegal, duplicate variable naming
3)因此,Action
/Func
是可重新分配的,并且可以指向任何函数,而方法一旦编译,则永远保持不变。Func/Action
如果它指向的方法在运行时从不改变,那么使用它在语义上是错误的。
bool IsIt() { return 1 > 2; } //always returns false
Func<bool> IsIt = () => 1 > 2;
IsIt = () => 2 > 1; //output of IsIt depends on the function it points to.
4)您可以为普通方法指定ref
/out
参数。例如,您可以拥有
bool IsIt(out string p1, ref int p2) { return 1 > 2; } //legal
Func<out string, ref int, bool> IsIt; //illegal
5)与方法不同,您不能为Action
/引入新的泛型类型参数Func
(顺便说一句,它们已经是泛型,但类型参数只能是父方法或类中指定的已知类型或类型)。
bool IsIt<A, R>() { return 1 > 2; } //legal
Func<bool> IsIt<A, R> = () => 1 > 2; //illegal
6)方法可以有可选参数,而不是Action
/ Func
。
bool IsIt(string p1 = "xyz") { return 1 > 2; } //legal
Func<string, bool> IsIt = (p1 = "xyz") => 1 > 2; //illegal
7)您可以params
为方法的参数使用关键字,而不是Action
/ Func
。
bool IsIt(params string[] p1) { return 1 > 2; } //legal
Func<params string[], bool> IsIt = p1 => 1 > 2; //illegal
8) Intellisense 可以很好地处理方法的参数名称(因此,您可以为方法提供很酷的 XML 文档),而不是Action
/ Func
。因此,就可读性而言,常规方法胜出。
9) Action
/Func
参数限制为 16(并不是说您不能定义自己的更多),但方法支持的数量超出您的需要。
至于何时使用哪个,我会考虑以下几点:
当您基于上述任何一点被迫使用一个时,那么您无论如何都别无选择。第 3 点是我发现的最有说服力的,你必须根据它做出决定。
在大多数正常情况下,常规方法是可行的方法。这是在 C# 和 VB.NET 世界中重构一组通用功能的标准方法。
根据经验,如果函数不止一行,我更喜欢方法。
如果函数在特定方法之外没有相关性并且函数太琐碎,比如简单的选择器 ( Func<S, T>
) 或谓词 ( Func<bool>
) 我更喜欢Action
/ Func
。例如,
public static string GetTimeStamp()
{
Func<DateTime, string> f = dt => humanReadable
? dt.ToShortTimeString()
: dt.ToLongTimeString();
return f(DateTime.Now);
}
在某些情况下Action
/Func
更有意义。例如,如果您必须构建一个繁重的表达式并编译一个委托,那么它值得只做一次并缓存已编译的委托。
public static class Cache<T>
{
public static readonly Func<T> Get = GetImpl();
static Func<T> GetImpl()
{
//some expensive operation here, and return a compiled delegate
}
}
代替
public static class Cache<T>
{
public static T Get()
{
//build expression, compile delegate and invoke the delegate
}
}
在您调用的第一种情况下Get
,GetImpl
仅执行一次,而在第二种情况下,Get
每次都会调用(昂贵的)。
Action 和 Func 是框架提供的委托类型。委托允许将函数视为变量,这意味着您可以(除其他外)将它们从一个方法传递到另一个方法。如果您曾经使用 C++ 进行过编程,您可以将 Delegates 视为函数指针,受其引用的方法的签名限制。
Action 和 Func 特别是具有一些最常见签名的通用委托(意味着它们采用类型参数)——大多数程序中的几乎任何方法都可以使用这两者中的一个或另一个来表示,从而节省了人们大量手动定义委托的时间,例如我们在版本 2 之前的 .net 中做过。事实上,当我在项目中看到这样的代码时,我通常可以安全地假设该项目是从 .net 1.1 迁移而来的:
// This defines a delegate (a type that represents a function)
// but usages could easily be replaced with System.Action<String>
delegate void SomeApplicationSpecificName(String someArgument);
我建议你多看看代表。它们是 C# 语言的一个非常强大的特性。
我用它们来创建一个函数数组。例如,我可能有一个 ComboBox,里面装满了可以采取的行动。我用类或结构的项目填充 ComboBox:
public class ComboBoxAction
{
private string text;
private Action method;
public ComboBoxAction(string text, Action method)
{
this.text = text;
this.method = method;
}
public override string ToString()
{
return this.text;
}
public void Go()
{
this.method();
}
}
然后当有人选择一个项目时,我可以调用该操作。
CType(ComboBox1.SelectedItem, ComboBoxAction).Go()
这比让 Select 语句根据 ComboBox 的文本确定要调用的方法要容易得多。
在很多情况下,Func 可以提供 Method 无法提供的帮助。
public void DoThing(MyClass foo, Func<MyClass, string> func)
{
foo.DoSomething;
var result = func(foo);
foo.DoStringThing(result);
}
因此,您可以在调用此方法时指定不同的 Func - 该DoThing
方法不需要知道正在做什么,只要知道它是什么都会返回一个字符串。
您可以在不使用 Func 关键字的情况下使用关键字来执行此操作delegate
;它的工作方式大致相同。
action
和的一个很好的用途func
是当我们需要执行一些操作时(在方法之前或之后),不管方法是什么。例如,如果发生异常,我们需要重试该方法 10 次。
考虑下面的方法——它的返回类型是generic
. 所以它可以应用于func
任何返回类型。
public static T ExecuteMultipleAttempts<T>(Func<T> inputMethod, Action additionalTask, int wait, int numOfTimes)
{
var funcResult = default(T);
int counter = 0;
while (counter < numOfTimes)
{
try
{
counter++;
funcResult = inputMethod();
//If no exception so far, the next line will break the loop.
break;
}
catch (Exception ex)
{
if (counter >= numOfTimes)
{
//If already exceeded the number of attemps, throw exception
throw;
}
else
{
Thread.Sleep(wait);
}
if (additionalTask != null)
{
additionalTask();
}
}
}
return funcResult;
}