为什么使用 Func<T,bool> 而不是 Predicate<T>?

231

这只是一个好奇的问题,我想知道是否有人有一个好的答案:

在.NET Framework类库中,我们例如有这两个方法:

public static IQueryable<TSource> Where<TSource>(
    this IQueryable<TSource> source,
    Expression<Func<TSource, bool>> predicate
)

public static IEnumerable<TSource> Where<TSource>(
    this IEnumerable<TSource> source,
    Func<TSource, bool> predicate
)
为什么他们使用Func<TSource, bool>而不是Predicate<TSource>?似乎Predicate<TSource>只被List<T>Array<T>使用,而Func<TSource, bool>则被大多数QueryableEnumerable方法和扩展方法使用...这是怎么回事?
4个回答

185

尽管在 .net 2.0 中引入了 Predicate,同时还有 List<T>Array<T>,但是不同的 FuncAction 变体来自于 .net 3.5。

因此,这些 Func 谓词主要用于一致性,以便在 LINQ 运算符中使用。关于使用 Func<T>Action<T>,.net 3.5 的指南说明:

应该使用新的 LINQ 类型 Func<>Expression<>,而不是自定义委托和谓词


3
实际上,您需要将其置于指南的背景下,该指南是关于编写LINQ提供程序的。所以是的,对于LINQ运算符,指南是使用Func<>和Expression<>作为扩展方法的参数。我同意,我希望有一个关于使用Func和Action的单独指南。 - Jb Evain
该指南的要点是,当实现LINQ操作符时,应使用Func<>和Expression<Func<>>。 - Jb Evain
该指南讨论了自定义委托和谓词,并且给出了一个定义自定义delegate bool Tester(int i)的示例。System.Predicate<T>不是自定义的。 - GSerg
@GSerg 刚想说同样的话。链接到的指南并不是反对使用 Predicate<T> 的论据(除非您认为 Predicate<T> 是自定义委托)。 - bornfromanegg

128

我之前也曾思考过。我喜欢Predicate<T>委托,它非常描述性。但是,您需要考虑Where的重载:

Where<T>(IEnumerable<T>, Func<T, bool>)
Where<T>(IEnumerable<T>, Func<T, int, bool>)

这样可以根据条目的索引进行过滤。这很好而且一致,而:

Where<T>(IEnumerable<T>, Predicate<T>)
Where<T>(IEnumerable<T>, Func<T, int, bool>)

不会的。

36

使用Func而不是特定委托的实际原因是C#将分别声明的委托视为完全不同的类型。

尽管Func<int, bool>Predicate<int>都具有相同的参数和返回类型,但它们不能进行赋值兼容。 因此,如果每个库都为每个委托模式声明自己的委托类型,那么这些库将无法互操作,除非用户插入“桥接”委托来执行转换。

    // declare two delegate types, completely identical but different names:
    public delegate void ExceptionHandler1(Exception x);
    public delegate void ExceptionHandler2(Exception x);

    // a method that is compatible with either of them:
    public static void MyExceptionHandler(Exception x)
    {
        Console.WriteLine(x.Message);
    }

    static void Main(string[] args)
    {
        // can assign any method having the right pattern
        ExceptionHandler1 x1 = MyExceptionHandler; 

        // and yet cannot assign a delegate with identical declaration!
        ExceptionHandler2 x2 = x1; // error at compile time
    }
通过鼓励所有人使用Func,微软希望能够缓解不兼容委托类型的问题。每个人的委托将友好地相互配合,因为它们将基于它们的参数/返回类型进行匹配。
这并不能解决所有问题,因为Func(和Action)不能具有out或ref参数,但这些用得较少。
更新:在评论中Svish说:
“仍然,将参数类型从Func更改为Predicate,再改回来,似乎没有任何区别?至少它仍然可以编译而没有任何问题。”
是的,只要您的程序只分配方法给委托,就像我的Main函数的第一行一样。编译器会默默地生成代码以新建一个委托对象,并将其转发到该方法。因此,在我的Main函数中,我可以将x1更改为ExceptionHandler2类型而不会引起问题。
然而,在第二行中,我尝试将第一个委托分配给另一个委托。即使第二个委托类型具有完全相同的参数和返回类型,编译器也会给出错误信息CS0029:“无法隐式将类型'ExceptionHandler1'转换为'ExceptionHandler2'”。
也许这会让它更清楚:
public static bool IsNegative(int x)
{
    return x < 0;
}

static void Main(string[] args)
{
    Predicate<int> p = IsNegative;
    Func<int, bool> f = IsNegative;

    p = f; // Not allowed
}

只要我直接将方法 IsNegative 分配给变量 pf,那么这是完全可以的。但是,如果我将其中一个变量分配给另一个变量,则会出现问题。


然而,将参数类型从Func<T, bool>切换到Predicate<T>,再切回去似乎没有任何区别?至少它仍然可以编译而没有任何问题。 - Svish
似乎微软正在试图阻止开发人员认为这些是相同的。我想知道为什么? - Matt Kocaj
1
如果你传递给方法的表达式已经被单独定义,那么切换参数类型确实会有所不同,因为它将被标记为 Func<T, bool>Predicate<T>,而不是由编译器推断的类型。 - Adam Ralph

35
建议(在3.5及以上版本中)使用 Action<...>Func<...> - 为什么?一个优点是,"Predicate<T>" 只有在你知道 "predicate" 是什么意思时才有意义 - 否则你需要查看对象浏览器等来找到签名。
相反,Func<T,bool> 遵循标准模式;我可以立即知道这是一个接受 T 并返回 bool 的函数 - 不需要理解任何术语 - 只需应用我的真值测试。
对于 "predicate" 这可能还可以,但我欣赏尝试标准化。它也允许与该领域相关的相关方法之间的很多平等。

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