我知道如何在.NET中使用Action
和Func
,但每次我开始时,使用我调用的常规旧方法可以实现完全相同的解决方案。
当Action
或Func
被用作我无法控制的事物的参数时,这就排除了,例如LINQ的.Where
。
所以基本上我的问题是......为什么这些存在?他们给了我什么额外的新东西,一个简单的方法没有?
答案 0 :(得分:29)
我认为其他答案在这里讨论Action
/ Func
是什么及其用途。我将尝试回答如何在Action
/ Func
和方法之间进行选择。差异首先是:
1)从原始性能的角度来看,delegates are slower compared to direct method calls,但它太微不足道了,担心这是一种不好的做法。
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
引入新的泛型类型参数(它们已经是通用的btw,但类型参数只能是已知类型或类型与方法不同,在父方法或类中指定。
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} }
至于何时使用,我会考虑以下内容:
当你被迫使用基于上述任何一点的那个时,你无论如何都没有别的选择。 第3点是我发现最引人注目的,你必须根据自己的决定做出决定。
在大多数正常情况下,常规方法是可行的方法。它是在C#和VB.NET世界中重构一组通用功能的标准方法。
根据经验,如果函数不仅仅是一行,我更喜欢一种方法。
如果函数在特定方法之外没有相关性且函数太简单了,比如简单的选择器(Func<S, T>
)或谓词(Func<bool>
)我宁愿{{1 }} / 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
。
不要忘记匿名方法本身将methods support more than you will ever need.与Get
无关,使得使用方式略有不同。另请参阅certain limits
答案 1 :(得分:23)
Action和Func是框架提供的Delegate类型。委托允许将函数视为变量,这意味着您可以(除其他外)将它们从方法传递给方法。如果您曾使用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#语言的一个非常强大的功能。
答案 2 :(得分:2)
我用它们来创建一个函数数组。例如,我可能有一个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的文本确定调用哪个方法容易得多。
答案 3 :(得分:2)
在很多情况下,Func可以帮助方法不会。
public void DoThing(MyClass foo, Func<MyClass, string> func)
{
foo.DoSomething;
var result = func(foo);
foo.DoStringThing(result);
}
因此,无论何时调用此方法,都可以指定不同的Func - DoThing
方法不需要知道正在执行的操作,只要它不管它将返回一个字符串。
您可以使用delegate
关键字而不使用Func关键字来执行此操作;它的工作原理大致相同。
答案 4 :(得分:1)
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;
}