【问题标题】:Why a `Predicate<T>` doesn't match a `Func<T,bool>`?为什么 `Predicate<T>` 与 `Func<T,bool>` 不匹配?
【发布时间】:2014-08-25 03:45:10
【问题描述】:

我尝试在 C# 中编译以下代码:

public static T FirstEffective(IEnumerable<T> list) 
{
    Predicate<T> pred = x => x != null;
    return Enumerable.FirstOrDefault(list, pred);
}

编译器 (Mono/.NET 4.0) 给出以下错误:

File.cs(139,47) The best overloaded method match for `System.Linq.Enumerable.FirstOrDefault<T>(this System.Collections.Generic.IEnumerable<T>,System.Func<T,bool>)' has some invalid arguments
/usr/lib/mono/4.0/System.Core.dll (Location of the symbol related to previous error)
File.cs(139,47): error CS1503: Argument `#2' cannot convert `System.Predicate<T>' expression to type `System.Func<T,bool>'

这很奇怪,因为 Predicate&lt;T&gt; 实际上是一个将参数 T 作为输入并返回 bool 的函数(T 甚至是“协变的”,因此允许 T 的特化)。代表们是否没有考虑“Liskov 替换原则”来推导出Predicate&lt;T&gt; 等价于Func&lt;T,bool&gt;?据我所知,这个等价问题应该是可判定的。

【问题讨论】:

  • 这是相关的,但没有解释为什么不执行等价检查。如前所述,委托类型的等价性是可以确定的......
  • 顺便问一句:C# 类型系统实际上不是图灵完备的吗?类型系统是否需要图灵完备才能使其不可判定?子类型化的一般问题是不可判定的;您可能会对这篇论文感兴趣:research.microsoft.com/pubs/64041/fool2007.pdf。 C# 在编写时还没有逆变,但现在有了,所以关于 IL 语言的部分适用于 C# 4.0。
  • @EricLippert:抱歉,确实选词不当。非常感谢。

标签: c# function delegates predicate


【解决方案1】:

C# 规范对此很清楚:

15.1 委托声明

C# 中的委托类型是名称等价的,而不是结构上等价的。 具体来说,两种不同的委托类型具有相同的 参数列表和返回类型被认为是不同的委托 类型

这就是您的代码无法编译的原因。

您可以通过调用委托而不是传递它来使其工作:

public static T FirstEffective (IEnumerable<T> list) {
    Predicate<T> pred = x => x != null;
    return Enumerable.FirstOrDefault (list, x => pred(x));
}

更新

Eric Lippert 有一篇很棒的博文:前微软 C# 团队成员,详细回答了您的问题:Delegates and structural identity

【讨论】:

  • 仍然想知道为什么会这样。由于委托之间的转换非常简单:没有真正的转换。相同的“方法指针”就足够了。
  • 这正是方法指针和 C# 委托之间的区别。与classes 相同:具有相同成员集的两个类不被视为等效。这就是 .NET 中类型系统的工作原理。
  • Eric 的博客链接已失效。我能找到的最好的方法是使用archive.org web.archive.org/web/20160331233359/http://blog.coverity.com/…
【解决方案2】:

委托类型不能隐式转换,即使它们具有所有相同的参数和返回信息。不过,您的情况有一个简单的解决方法。您可以在您的委托实例上使用.Invoke 方法。

public static T FirstEffective<T>(IEnumerable<T> list)
{
    Predicate<T> pred = x => x != null;
    return Enumerable.FirstOrDefault(list, pred.Invoke);
}

至于为什么代表以这种方式工作的问题,答案是这是一个设计决定。具有相同公共接口的类也不是隐式可转换的,因此并不是真正的不一致。

【讨论】:

  • 这是一个不错的解决方案。但是很奇怪,代表不被认为是等价的......
  • 是的,但那是因为它们有不同的字段,......而且如何转换它们并非易事。使用数组的List&lt;T&gt; 与使用LinkedList&lt;T&gt; 方法的列表完全不同。
  • 如果我创建两个类,每个类只有一个public string Foo { get; set; },即使它们的定义相同,我也无法互换使用它们。我认为这是一个类似的案例。
  • 是的 @recursive 是对的——这是 OOP 语言中的常见行为——类/接口/等的名称。重要的是,不是签名。
  • @recursive:有一个区别:C# 中的编程代码是图灵完备的。因此,检查两个类是否相同是无法确定的(当然可以检查这个简单的例子)。然而,类型系统不是图灵完备的,因此可以检查两个签名是否相同......
【解决方案3】:

很晚,但巧合的是,我偶然发现了同样的问题,准确的答案可以在这里找到:important comment

基本上,这意味着这是一种基于不幸决定以这种方式实施它的不一致。虽然predicate&lt;T&gt; == func&lt;T, bool&gt;,尽管签名相同,但它们是不同的类型。我想出于向后兼容性的原因,可以转换表达式和/或 lambda,然后通过 new predicate&lt;T&gt;(func&lt;T, bool&gt;) 返回谓词。

【讨论】:

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