为什么用Func <T,bool>代替Predicate <T>?


210

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

在.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方法以及扩展方法都使用...,这是怎么回事?


21
糟糕,这些用法的不一致也使我发疯。
乔治·莫尔

Answers:


170

虽然Predicate已经在同一时间被引入List<T>,并Array<T>在.NET 2.0中,不同的FuncAction变异都来自.NET 3.5。

因此,这些Func谓词主要用于LINQ运算符中的一致性。作为.NET 3.5,有关使用的Func<T>Action<T>方针状态

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


7
太酷了,以前从未见过这些公会=)
Svish

6
由于它的票数最多,因此将接受此作为答案。并且因为乔恩·斯基特(Jon Skeet)有很多代表...:p
Svish

4
从书面上看,这是一个奇怪的准则。当然,它应该声明“不要使用新的LINQ类型“ Func <>”和“ Action <>” [...]”。Expression <>是完全不同的东西。
乔恩·斯基特

3
好吧,实际上您必须将其放在准则的上下文中,这是关于编写LINQ提供程序的。是的,对于LINQ运算符,指导原则是使用Func <>和Expression <>作为扩展方法的参数。我同意我对使用Func和Action的单独指南表示赞赏
Jb Evain 09年

我认为Skeet的观点是Expression <>不是准则。这是C#编译器的功能,可以识别该类型并对其进行不同的处理。
Daniel Earwicker 09年

116

我以前想知道。我喜欢这位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>)

不会。


11
Predicate <int,bool>会有些丑陋-谓词通常(基于计算机科学的IME)基于单个值进行谓词。它当然可以是Predicate <Pair <T,int >>,但这更丑陋:)
Jon Skeet

如此真实...呵呵。不,我想Func比较干净。
Svish

2
由于指导原则,接受了其他答案。希望我能标出多个答案!如果我可以将多个答案标记为“非常值得关注”或“除了答案之外,还应指出一个要点,那就太好了”
Svish

6
嗯,只是看到我写错了第一条评论:P我的建议当然是Predicate <T,int> ...
Svish

4
@JonSkeet我知道这个问题已经很老了,但是知道什么具有讽刺意味吗?Where扩展方法中的参数名称为predicate。嘿= P。
康拉德·克拉克

32

当然使用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,Microsoft希望这将减轻不兼容的委托类型的问题。每个人的代表都可以一起玩,因为他们只是根据他们的参数/返回类型进行匹配。

它不能解决所有问题,因为Func(和Action)不能具有outref参数,但这些参数较少使用。

更新: Svish在评论中说:

不过,将参数类型从Func切换为Predicate并返回,似乎没有什么区别?至少它仍然可以毫无问题地进行编译。

是的,只要您的程序仅将方法分配给委托即可,就像我Main函数的第一行一样。编译器以静默方式生成代码以创建新的委托对象,该委托对象继续转发到方法。因此,在我的Main函数中,我可以更改x1为type ExceptionHandler2而不引起问题。

但是,在第二行中,我尝试将第一个委托分配给另一个委托。即使认为第二个委托类型具有完全相同的参数和返回类型,编译器也会给出error CS0029: Cannot implicitly convert type 'ExceptionHandler1' to '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
}

只要直接为and 变量赋值,我的方法IsNegative就是一件非常好的事情。但是,然后我无法将这些变量之一分配给另一个。pf


仍然,将参数类型从Func <T,bool>切换到Predicate <T>并返回,似乎没有什么区别?至少它仍然可以毫无问题地进行编译。
Svish

似乎MS试图阻止开发人员认为它们相同。我想知道为什么?
马特·科卡伊

1
如果您要传递给它的表达式已单独定义给方法调用,则切换参数类型确实会有所不同,因为从那时起,它将被键入为Func<T, bool>Predicate<T>而不是由编译器推断出的类型。
亚当·拉尔夫

30

(3.5英寸以上)的建议是使用Action<...>Func<...>- “为什么?”为 -一个优点是“ Predicate<T>”仅在您知道“谓词”的含义时才有意义-否则,您需要查看对象浏览器(等)以找到签名。

相反地​​,Func<T,bool>遵循标准模式;我可以立即看出这是一个需要a T并返回a 的函数bool-不需要了解任何术语-只需应用我的真实测试即可。

对于“谓词”,这可能还可以,但是我赞赏标准化的尝试。它还允许与该区域中的相关方法保持一致。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.