手动创建委托与使用 Action/Func 委托
Posted
技术标签:
【中文标题】手动创建委托与使用 Action/Func 委托【英文标题】:Creating delegates manually vs using Action/Func delegates 【发布时间】: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<T>
,而 .NET 3.5 出现了 Func<T>
?
【问题讨论】:
【参考方案1】:Action
和 Func
委托家族的出现使得自定义委托的使用减少了,但后者仍然找到了用途。自定义委托的优点包括:
正如其他人所指出的,与通用 Action
和 Func
不同,它清楚地传达了意图(Patrik 对有意义的参数名称有很好的看法)。
您可以指定ref
/out
参数,这与其他两个通用委托不同。例如,您可以拥有
public delegate double ChangeListAction(out string p1, ref int p2);
但不是
Func<out string, ref int, double> ChangeListAction;
1234563超过。在后一种情况下更改签名会很麻烦 - 不干的坏情况。
可以有可选参数。
public delegate double ChangeListAction(string p1 = "haha", int p2);
但不是
Func<string, int, double> ChangeListAction = (p1 = "haha", p2) => (double)p2;
您可以为方法的参数使用 params
关键字,Action/Func
则不然。
public delegate double ChangeListAction(int p1, params string[] p2);
但不是
Func<int, params string[], double> ChangeListAction;
好吧,如果你真的不走运并且需要超过 16 个的参数(目前):)
关于Action
和Func
的优点:
它又快又脏,我一直在用它。如果用例很简单(自定义委托对我来说已经过时了),它会缩短代码。
更重要的是,它的类型跨域兼容。 Action
和 Func
是框架定义的,只要参数类型匹配,它们就可以无缝运行。 ChangeSomeAction
不能用于 ChangeListAction
。 Linq
很好地利用了这个方面。
【讨论】:
这是另一个你不能使用 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显式声明委托可以帮助进行一些类型检查。编译器可以确保分配给变量的委托旨在用作 ChangeListAction,而不是一些恰好与签名兼容的随机操作。
然而,声明自己的委托的真正价值在于它赋予了它语义意义。阅读代码的人将通过其名称知道代表在做什么。想象一下,如果您有一个包含三个 int 字段的类,但您声明了一个包含三个 int 元素的数组。数组可以做同样的事情,但字段的名称会带来对开发人员有用的语义信息。
在设计像 LINQ 这样的通用库时,应该使用 Func、Predicate 和 Action 委托。在这种情况下,除了它们将执行和操作或用作谓词这一事实之外,委托没有预定义的语义。
在旁注中,元组与匿名类型与声明自己的类之间存在类似的权衡问题。您可以将所有内容都粘贴在 Tuple 中,但属性只是 Item1、Item2,它没有说明类型的使用。
【讨论】:
【参考方案4】:正如一些答案提到的那样,胜利是清晰的,你命名类型,这样你的 api 用户会更容易理解。我想说 - 在大多数情况下 - 为您的公共 api 声明委托类型,但在内部使用 Func<?,?>
是完全可以的。
声明其他答案中未提及的委托类型的一个巨大好处是,除了为类型命名之外,您实际上还可以为参数命名,这将大大提高可用性。
【讨论】:
【参考方案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<T>/Func<T> ?
【讨论】:
【参考方案6】:当你开始在 Func/Action 中获取太多参数时,显式声明委托,否则你不得不回头看,“第二个 int 又是什么意思?”
【讨论】:
【参考方案7】:要获得更好和更详细的答案,请查看@nawfal。我会尽量简单一点。
你声明了一个类的成员,所以你应该坚持使用委托。使用delegate
更具描述性和结构性。
Action/Func
类型是为传递而设计的,因此您应该更多地将它们用作参数和局部变量。
实际上它们都继承了Delegate
类。 Action 和 Func 是泛型类型,可以简化创建具有不同参数类型的委托。而 delegate 关键字实际上在一个声明中创建了继承自 Delegate 的全新类。
【讨论】:
【参考方案8】:正如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<>
委托的参数。我们可以用这个方法做的事情是
Declare
Func<>
类型的委托并将其传递给函数而不是Define
自定义委托。
例如,我从上面的代码中添加了更多代码
string[] strList = "abc", "abcd", "abcdef" ;
strList.Select(fAssign); // is valid
//strList.Select(customDlg); // Compilation Error!!
【讨论】:
以上是关于手动创建委托与使用 Action/Func 委托的主要内容,如果未能解决你的问题,请参考以下文章
在一行中声明和定义一个委托(不使用 Action/Func)
委托delegate,Action,Func,Predicate