手动创建委托 vs 使用 Action/Func 委托

80

今天我在考虑声明这个:

private delegate double ChangeListAction(string param1, int number);

但为什么不使用这个:

private Func<string, int, double> ChangeListAction;

如果ChangeListAction没有返回值,我可以使用以下方法:

private Action<string,int> ChangeListAction;

使用 delegate 关键字声明委托的优势在哪里?

这是因为.NET 1.1,而随着.NET 2.0的到来出现了 Action<T>,并且随着.NET 3.5的到来出现了 Func<T> 吗?

8个回答

92
< p > ActionFunc 委托家族的出现使得自定义委托使用较少,但后者仍然有用。自定义委托的优点包括:

  1. As others have pointed, conveys intent clearly unlike generic Action and Func (Patrik has a very good point about meaningful parameter names).

  2. You can specify ref/out parameters unlike the other two generic delegates. For eg, you can have

    public delegate double ChangeListAction(out string p1, ref int p2);
    

    but not

    Func<out string, ref int, double> ChangeListAction;
    
  3. Also, with custom delegates you need to write ChangeListAction (I mean the definition) only once somewhere in your code base, whereas if you don't define one you will have to litter everywhere Func<string, int, double> all over. Changing the signature will be a hassle in the latter case - a bad case of not being dry.

  4. Can have optional parameters.

    public delegate double ChangeListAction(string p1 = "haha", int p2);
    

    but not

    Func<string, int, double> ChangeListAction = (p1 = "haha", p2) => (double)p2; 
    
  5. You can have params keyword for parameters of a method, not so with Action/Func.

    public delegate double ChangeListAction(int p1, params string[] p2);
    

    but not

    Func<int, params string[], double> ChangeListAction;
    
  6. Well, if you're truly out of luck and need parameters more than 16 (for the moment) :)


关于ActionFunc的优点:

  1. 它快速而简单,我在很多地方都使用它。如果用例是琐碎的话(自定义委托已经不再流行),则可以使代码更短。

  2. 更重要的是,它跨领域兼容。 ActionFunc是框架定义的,只要参数类型匹配,它们就无缝运作。您不能为ChangeListAction创建ChangeSomeActionLinq非常善于利用这个方面。


2
这是另一个你不能使用Func的例子 - 如果它必须像这里提到的那样返回自身,你就不能使用Func:https://dev59.com/questions/-l4c5IYBdhLWcg3wXZYC - Marwie
感谢@Marwie。非常有帮助。我会在适当的时候将其添加到我的答案中。 - nawfal
在示例#5中,params必须是最后一个参数。 - Jalal
@Todd,说实话我不明白你的意思。我的回答跟LINQ没有关系。 - nawfal
@Todd 也许没有什么意义,但我也想澄清一下:Action/Func并不是lambda表达式的基础。后者可以与任何委托类型一起使用。Action/Func只是使匿名方法更易于访问/使用。 - nawfal
显示剩余11条评论

66

优点是清晰明了。通过为类型赋予显式名称,读者更容易理解它的作用。

这也有助于您编写代码。像这样的错误:

cannot convert from Func<string, int, double> to Func<string, int, int, double>

比起那种说:

cannot convert from CreateListAction to UpdateListAction

这也意味着,如果您有两个不同的委托,它们都接受相同类型的参数但在概念上完全执行两个不同的操作,编译器可以确保您不会意外地在一个位置使用另一个。


1
Func<T1,T2,U>的名称类似于Func<T1,T2,T3,U>,因此编译器不应该说:“无法将Func1Name转换为Func2Name”,否则它将不会更有帮助。 - Elisabeth

11

正如一些答案所提到的,胜利在于清晰。你可以为你的API命名类别,这样用户就更容易理解了。我会说,在大多数情况下,为你的公共API声明委托类型是很好的选择,但在内部使用Func<?,?>也完全可以。

为委托类型声明名称的一个巨大好处是,除了给类型命名之外,你实际上还可以为参数命名,这将极大地增加其可用性。


10

显式声明代理可以帮助进行某些类型检查。编译器可以确保分配给变量的代理是要用作ChangeListAction,而不是某个与该签名兼容的随机操作。

然而,声明自己的代理的真正价值在于它赋予了语义含义。阅读代码的人会根据其名称知道代理正在执行什么操作。想象一下,如果您有一个具有三个int字段的类,但是您声明了一个由三个int元素组成的数组。数组可以做同样的事情,但字段的名称提供了对开发人员有用的语义信息。

当您设计像LINQ这样的通用库时,应使用Func、Predicate和Action代理。在这种情况下,代理除了执行操作或用作谓词之外,没有预定义的语义。

另一方面,Tuple vs匿名类型 vs声明自己的类也存在类似的权衡问题。您可以将所有内容都放在Tuple中,但属性仅为Item1、Item2等,无法说明该类型的使用。


7

一个特殊的使用情况,只能使用委托:

public delegate bool WndEnumProc(IntPtr hwnd, IntPtr lParam);
[DllImport("User32.dll")]
public static extern bool EnumWindows(WndEnumProc lpEnumFunc, IntPtr lParam);

使用 Func/Action 并不起作用:'Namespace.Class.WndEnumProc' 是一个 'field',但被用作 'type'
public Func<IntPtr, IntPtr, bool> WndEnumProc;
[DllImport("User32.dll")]
public static extern bool EnumWindows(WndEnumProc lpEnumFunc, IntPtr lParam);

以下代码可以编译,但在运行时会抛出异常,因为 System.Runtime.InteropServices.DllImportAttribute 不支持通用类型的封送:
[DllImport("User32.dll")]
public static extern bool EnumWindows(Func<IntPtr, IntPtr, bool> lpEnumFunc, IntPtr lParam);

我提供这个例子来向大家展示:有时候委托是你唯一的选择。而这也是回答你为什么不使用 Action<T>/Func<T> 的合理答案。


4

当Func/Action中的参数过多时,要显式声明委托,否则你将不断回头看,“第2个int是什么意思?”


3
为了更好、更详细的答案,请参考 @nawfal。我将试着更简单易懂。
你正在声明一个类的成员,所以你应该坚持使用委托(delegate)。使用“delegate”更具描述性和结构性。
Action/Func类型用于传递,因此应在参数和局部变量中更多地使用它们。
实际上,这两个类型都继承自Delegate类。Action和Func是通用类型,并且简化了创建具有不同参数类型的委托。而delegate关键字实际上会在一个声明中创建一个完整的新类,该类继承自Delegate。

1
MSDN所述,Func<>本身是预定义的Delegate。一开始我对这个东西感到困惑,但经过实验后,我的理解更加清晰了。通常在C#中,我们可以将Type视为指向Instance的指针。
同样的概念也适用于Delegate,它被视为指向Method的指针。这两者之间的区别在于,Delegate不具有面向对象编程(OOP)的概念,例如Inheritance。为了使这些概念更加清晰,我进行了实验。
public delegate string CustomDelegate(string a);

// Func<> is a delegate itself, BUILD-IN delegate
//==========
// Short Version Anonymous Function
Func<string, string> fShort = a => "ttt";
//----------
// Long Version Anonymous Function
Func<string, string> fLong = delegate(string a)
{
  return "ttt";
};
//----------
MyDelegate customDlg;
Func<string, string> fAssign;
// if we do the thing like this we get the compilation error!!
// because fAssign is not the same KIND as customDlg
//fAssign = customDlg;

许多框架中都有内置方法(例如LINQ),接收Func<>委托的参数。我们可以使用此方法的事情是

声明Func<>类型的委托并将其传递给函数,而不是定义自定义委托。

例如,从上面的代码中,我添加了更多代码。

string[] strList = { "abc", "abcd", "abcdef" };
strList.Select(fAssign); // is valid
//strList.Select(customDlg); // Compilation Error!!

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接