Func vs. Action vs. Predicate
有了真实的例子和它们的用法,有人能帮助我理解:
我们什么时候需要Func代表?
我们什么时候需要行动代表?
何时需要谓词委托?
- 尽管这是一个副本,但它有一个更为彻底的公认答案。
- @有趣的是,两个公认的答案都来自乔恩·斯基特。
Func和Action之间的区别只是您是否希望代理返回值(使用Func)或不返回值(使用Action)。
Func可能是LINQ中最常用的——例如在投影中:
1
| list.Select(x => x.SomeProperty) |
或过滤:
1
| list.Where(x => x.SomeValue == someOtherValue) |
或键选择:
1
| list.Join(otherList, x => x.FirstKey, y => y.SecondKey, ...) |
Action更常用于List.ForEach之类的事情:对列表中的每个项目执行给定的操作。我使用这个的频率比Func少,尽管有时我会对Control.BeginInvoke和Dispatcher.BeginInvoke这样的东西使用无参数版本。
Predicate只是一个特例Func真的,是在所有Func和大多数Action代表都来之前介绍的。我怀疑,如果我们已经有了各种形式的Func和Action,Predicate就不会被引入……虽然它确实赋予了委托人的使用某种意义,但Func和Action用于广泛不同的目的。
Predicate主要用于List中的方法,如FindAll和RemoveAll。
- 我更喜欢在函数签名中看到Predicate。它说明了传递的方法做出了一个决定,而不仅仅返回一个成功的代码或一个不同类型的bool。
- 是否有任何设计指导方针来选择Predicate或Func?我喜欢Predicate的表现力,但我也看到过Func的推荐。
- @罗恩:是的,这就是我倒数第二段的内容。
- @我没见过。linq在整个过程中使用Func,但这可能是为了一致性,因为许多接受Func作为谓词的方法也有一个重载,将Func作为索引谓词。
- 我有点喜欢你解释得多么简单。func-返回操作-执行(方法)
- 谁认为有三个不同的名字来称呼同一件事会有用呢?它只是一个函数指针!
- @琼斯基特,是的,但是你的第二段到最后一段读起来就像是一段事情发展的历史,把这个意图隐藏在其中,让它看起来像一个离题的评论。@Ron的评论很清楚,它是一个理想的效果(不仅仅是软件进化的副产品),并且指出在某些用例中可以有意选择使用Predicate,而在其他用例中使用Func<…,bool>来传达意义。巧合的是,你的倒数第二段和@ron的评论之间的区别就像使用Func和Predicate之间的区别。
- 在C++中,谓词的概念也意味着函数被假定为"纯函数",即如果给定相同的输入,它应该总是产生相同的结果,而生成布尔函数的简单函数不给出这样的保证。当然,这不是由编译器或任何东西强制执行的,而是显式地声明一个函数参数是一个"谓词"有助于记录这种假设(在某些情况下,例如多线程情况下,这可能非常重要)。
- 请注意,LINQ(或C和VB的真正过载分辨率)将允许Func,其中它需要Predicate,但反之则不允许:"无法从'System.Predicate'转换为'System.Func'"。
- 这意味着,如果您为少数已经在Where子句中使用Predicate的情况创建了Where(this s, Predicate p)扩展方法,那么所有Where子句都会突然与这个新的扩展方法相匹配(包括现在在内部递归使用的方法,希望它继续引用现有的Where。]扩展方法:—()。
- @马赫德:我必须看到确切的情况,才能理解为什么超载决议是这样的行为。请注意,lambda表达式可以转换为Func或Predicate与现有Predicate的引用之间存在很大差异。要调用原始扩展方法,只需将其作为静态方法调用:Enumerable.Where(...)。
- 我注意到的部分内容是针对vb.net的:据我所知,在c中没有与Dim IsEven = Function(x As Integer) ((x And 1) = 0)相同的内容。等效的var IsEven = (int x) => ((x & 1) == 0);抱怨:"不能将lambda表达式赋给隐式类型的局部变量"。
- @Markhurd:看起来VB为这种情况创建了一个新的委托类型。
- 这意味着queryable.Where(IsEven)只是IEnumerable使用linq提供的.Where扩展,但当我提供我自己的Predicate基扩展(一个用于IEnumerable一个用于IQueryable时,它是IQueryable。
- 为什么要将LINQ作为与C_的核心特性不相关的主题的示例?对那些从不使用LINQ的人真的没什么帮助
- @先生:那么第一段的内容还包括在内。不是说这是C本身的一个特性——它们只是委托定义。
操作是一个方法的委托(指针),它接受零个、一个或多个输入参数,但不返回任何内容。
func是一个方法的委托(指针),它接受零个、一个或多个输入参数,并返回一个值(或引用)。
谓词是一种特殊的func,常用于比较。
尽管与LINQ一起广泛使用,但action和func在逻辑上独立于LINQ。C++已经包含了类型化函数指针形式的基本概念。
下面是一个不使用linq的action和func的小示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| class Program
{
static void Main (string[] args )
{
Action <int> myAction = new Action <int>(DoSomething );
myAction (123); // Prints out"123"
// can be also called as myAction.Invoke(123);
Func <int, double> myFunc = new Func <int, double>(CalculateSomething );
Console .WriteLine(myFunc (5)); // Prints out"2.5"
}
static void DoSomething (int i )
{
Console .WriteLine(i );
}
static double CalculateSomething (int i )
{
return (double)i /2;
}
} |
- 谓词是接受泛型参数并返回bool的委托
- 很好的例子!请注意,您也可以通过简单地调用myaction:myAction(123);。你不需要使用.Invoke()。
- 为了完整起见,如果要使用不带参数但返回值(或引用)的func,则应该编写类似funcmyfoo=new func(foo);foo定义可以是static double foo()return 1.0;
- @马丁谓词只接受一个参数。
func-当您需要一个函数的委托,该函数可以接受参数,也可以不接受参数,并返回一个值。最常见的例子是从LINQ中选择:
1
| var result = someCollection .Select( x => new { x .Name, x .Address }); |
操作-当您需要一个函数的委托,该函数可以接受参数,也可以不返回值。我经常将这些用于匿名事件处理程序:
1
| button1.Click += (sender, e) => { /* Do Some Work */ } |
谓词-当需要一个专门的func版本时,该版本根据一组条件计算一个值并返回一个布尔结果(匹配为true,否则为false)。同样,在linq中,这些经常用于以下情况:
1 2
| var filteredResults =
someCollection.Where(x => x.someCriteriaHolder == someCriteria); |
< /打击>
我只是反复检查了一下,结果发现Linq不使用谓词。不知道他们为什么做这个决定……但理论上,它仍然是一个谓词适合的情况。
- 我知道这很古老,但是谓词并没有被linq使用,因为它早于我们今天所知道的func和action,后两个谓词可以更好地实现完全相同的结果。
- @格兰特,为什么更好?我认为,当使用有意义的命名委托而不是Func或Action时,可读性更好。
- 这取决于如何使用委托,但有时名称并不重要,只需为一次使用而创建函数和变量会降低可读性。