【问题标题】:Why Func<T,bool> instead of Predicate<T>?为什么使用 Func<T,bool> 而不是 Predicate<T>?
【发布时间】:2010-10-14 12:13:47
【问题描述】:

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

在 .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&lt;TSource, bool&gt; 而不是Predicate&lt;TSource&gt;?似乎Predicate&lt;TSource&gt; 仅被List&lt;T&gt;Array&lt;T&gt; 使用,而Func&lt;TSource, bool&gt; 几乎被所有QueryableEnumerable 方法和扩展方法使用......这是怎么回事?

【问题讨论】:

  • 呃,是的,这些不一致的用法也让我发疯。

标签: c# .net predicate func


【解决方案1】:

虽然PredicateList&lt;T&gt;Array&lt;T&gt; 同时被引入,但在.net 2.0 中,不同的FuncAction 变体来自.net 3.5。

所以那些Func 谓词主要用于LINQ 运算符的一致性。从 .net 3.5 开始,关于使用 Func&lt;T&gt;Action&lt;T&gt; guideline states

请使用新的 LINQ 类型 Func&lt;&gt;Expression&lt;&gt; 而不是自定义 委托和谓词

【讨论】:

  • 酷,以前从未见过这些规范 =)
  • 会接受这个作为答案,因为它拥有最多的选票。因为乔恩斯基特有很多代表......:p
  • 这是一个奇怪的书面指南。当然它应该声明“使用新的 LINQ 类型“Func”和“Action”[...]”。表达式 是完全不同的东西。
  • 嗯,实际上你必须将它放在指南的上下文中,这是关于编写 LINQ 提供程序的。所以是的,对于 LINQ 运算符,指南是使用 Func 和 Expression 作为扩展方法的参数。我同意,如果有关于使用 Func 和 Action 的单独指南,我将不胜感激
  • 我认为 Skeet 的观点是 Expression 不是指导方针。这是一个 C# 编译器功能,可以识别该类型并对其执行不同的操作。
【解决方案2】:

我以前也想过这个问题。我喜欢Predicate&lt;T&gt; 代表 - 它很好而且具有描述性。不过需要考虑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>)

不会。

【讨论】:

  • Predicate 会有些难看 - 谓词通常(计算机科学的 IME)基于单个值。它当然可以是 Predicate> ,但那更难看 :)
  • 太真实了……呵呵。不,我猜 Func 更干净。
  • 根据指南接受了其他答案。希望我可以标记多个答案!如果我可以将一些答案标记为“非常值得一提”或“除了答案之外还有一个值得一提的好点”
  • 嗯,刚刚看到我写错了第一条评论:P 我的建议当然是 Predicate...
  • @JonSkeet 我知道这个问题很老了,但知道什么是讽刺吗? Where扩展方法中的参数名称为predicate。嘿=P。
【解决方案3】:

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

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

    // 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)不能有outref 参数,但那些不太常用。

更新:在 cmets Svish 中说:

仍然,从 函数到谓词和 回来,似乎没有做任何 区别?至少它仍然可以编译 没有任何问题。

是的,只要您的程序只将方法分配给委托,就像我的Main 函数的第一行一样。编译器默默地生成代码以新的委托对象转发到方法。所以在我的Main 函数中,我可以将x1 更改为ExceptionHandler2 类型而不会造成问题。

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

我的方法IsNegative 非常适合分配给pf 变量,只要我直接这样做。但是我不能将其中一个变量分配给另一个变量。

【讨论】:

  • 仍然,将参数类型从 Func 切换到 Predicate 并返回,似乎没有任何区别?至少它仍然可以毫无问题地编译。
  • 似乎 MS 试图阻止开发人员认为这些是相同的。我想知道为什么?
  • 如果您传递给它的表达式已单独定义到方法调用,则切换参数类型确实会有所不同,因为那时它将被键入为Func&lt;T, bool&gt;Predicate&lt;T&gt;,而不是具有编译器推断的类型。
【解决方案4】:

建议(在 3.5 及更高版本中)是使用 Action&lt;...&gt;Func&lt;...&gt; - 用于“为什么?” - 一个优点是“Predicate&lt;T&gt;”只有在您知道“谓词”的含义时才有意义 - 否则您需要查看对象浏览器(等)才能找到签名。

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

对于“谓词”,这可能没问题,但我很欣赏标准化的尝试。它还允许与该领域的相关方法进行大量对等。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-05
    • 1970-01-01
    • 2010-10-18
    相关资源
    最近更新 更多