【问题标题】:Creating delegates manually vs using Action/Func delegates手动创建委托与使用 Action/Func 委托
【发布时间】:2011-05-27 19:48:54
【问题描述】:

今天我正在考虑宣布这一点:

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&lt;T&gt;,而 .NET 3.5 出现了 Func&lt;T&gt;

【问题讨论】:

    标签: c# delegates action func


    【解决方案1】:

    ActionFunc 委托家族的出现使得自定义委托的使用减少了,但后者仍然找到了用途。自定义委托的优点包括:

    1. 正如其他人所指出的,与通用 ActionFunc 不同,它清楚地传达了意图(Patrik 对有意义的参数名称有很好的看法)。

    2. 您可以指定ref/out 参数,这与其他两个通用委托不同。例如,您可以拥有

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

      但不是

      Func<out string, ref int, double> ChangeListAction;
      
    3. 1234563超过。在后一种情况下更改签名会很麻烦 - 不干的坏情况。
    4. 可以有可选参数。

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

      但不是

      Func<string, int, double> ChangeListAction = (p1 = "haha", p2) => (double)p2; 
      
    5. 您可以为方法的参数使用 params 关键字,Action/Func 则不然。

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

      但不是

      Func<int, params string[], double> ChangeListAction;
      
    6. 好吧,如果你真的不走运并且需要超过 16 个的参数(目前):)


    关于ActionFunc的优点:

    1. 它又快又脏,我一直在用它。如果用例很简单(自定义委托对我来说已经过时了),它会缩短代码。

    2. 更重要的是,它的类型跨域兼容。 ActionFunc 是框架定义的,只要参数类型匹配,它们就可以无缝运行。 ChangeSomeAction 不能用于 ChangeListActionLinq 很好地利用了这个方面。

    【讨论】:

    • 这是另一个你不能使用 Func 的地方 - 如果它必须返回自己,你就不能使用 Func,如此处提到的:*.com/questions/27989296/…
    • 感谢@Marwie。有帮助。会在某个时候添加到我的答案中。
    • 在例子#5中params必须是最后一个参数。
    • @Jalal 你是对的。愚蠢的错误。任何人都可以随时编辑答案:)
    • 您在“LINQ”中提到了这一点。我相信 Action/Func 是匿名(闭包)方法的基础。
    【解决方案2】:

    优点是清晰。通过为类型指定一个明确的名称,读者可以更清楚地了解它的作用。

    它还会在您编写代码时为您提供帮助。像这样的错误:

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

    没有那么有用:

    cannot convert from CreateListAction to UpdateListAction
    

    这也意味着,如果你有两个不同的委托,它们都采用相同类型的参数,但在概念上做了两件完全不同的事情,编译器可以确保你不会意外地使用你想要的另一个。

    【讨论】:

    • Func 的名称类似于 Func 所以编译器不应该说:Can not convert from Func1Name to Func2Name then it would not be不太有用。
    【解决方案3】:

    显式声明委托可以帮助进行一些类型检查。编译器可以确保分配给变量的委托旨在用作 ChangeListAction,而不是一些恰好与签名兼容的随机操作。

    然而,声明自己的委托的真正价值在于它赋予了它语义意义。阅读代码的人将通过其名称知道代表在做什么。想象一下,如果您有一个包含三个 int 字段的类,但您声明了一个包含三个 int 元素的数组。数组可以做同样的事情,但字段的名称会带来对开发人员有用的语义信息。

    在设计像 LINQ 这样的通用库时,应该使用 Func、Predicate 和 Action 委托。在这种情况下,除了它们将执行和操作或用作谓词这一事实之外,委托没有预定义的语义。

    在旁注中,元组与匿名类型与声明自己的类之间存在类似的权衡问题。您可以将所有内容都粘贴在 Tuple 中,但属性只是 Item1、Item2,它没有说明类型的使用。

    【讨论】:

      【解决方案4】:

      正如一些答案提到的那样,胜利是清晰的,你命名类型,这样你的 api 用户会更容易理解。我想说 - 在大多数情况下 - 为您的公共 api 声明委托类型,但在内部使用 Func&lt;?,?&gt; 是完全可以的。

      声明其他答案中未提及的委托类型的一个巨大好处是,除了为类型命名之外,您实际上还可以为参数命名,这将大大提高可用性。

      【讨论】:

        【解决方案5】:

        我发现了一个只能使用委托的特殊用例:

        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' is a 'field' but is used like a '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);
        

        我举这个例子是为了向大家展示:有时委托是你唯一的选择。这是您的问题why not use Action&lt;T&gt;/Func&lt;T&gt; ?

        的合理答案

        【讨论】:

          【解决方案6】:

          当你开始在 Func/Action 中获取太多参数时,显式声明委托,否则你不得不回头看,“第二个 int 又是什么意思?”

          【讨论】:

            【解决方案7】:

            要获得更好和更详细的答案,请查看@nawfal。我会尽量简单一点。

            你声明了一个类的成员,所以你应该坚持使用委托。使用delegate 更具描述性和结构性。

            Action/Func 类型是为传递而设计的,因此您应该更多地将它们用作参数和局部变量。

            实际上它们都继承了Delegate 类。 Action 和 Func 是泛型类型,可以简化创建具有不同参数类型的委托。而 delegate 关键字实际上在一个声明中创建了继承自 Delegate 的全新类。

            【讨论】:

              【解决方案8】:

              正如MSDN 所说,Func&lt;&gt; 本身就是预定义的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&lt;&gt;委托的参数。我们可以用这个方法做的事情是

              DeclareFunc&lt;&gt; 类型的委托并将其传递给函数而不是 Define 自定义委托。

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

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

              【讨论】:

                最近更新 更多