为什么使用`Predicate<;T>;`不会';t匹配`Func<;T、 bool>;`

本文关键字:lt gt 匹配 Func bool 为什么 不会 Predicate | 更新日期: 2023-09-27 18:19:26

我尝试在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<T>实际上是一个将参数T作为输入并返回bool的函数(T甚至是"协变的",因此允许T的特殊化)。代表们是否没有考虑"利斯科夫替代原则"得出Predicate<T>等同于Func<T,bool>?据我所知,这个等价问题应该是可判定的。

为什么使用`Predicate<;T>;`不会';t匹配`Func<;T、 bool>;`

C#规范对此很清楚:

15.1代表声明

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

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

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

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

更新

EricLippert是微软C#团队的前成员,他写了一篇很棒的博客文章,详细地回答了你的问题:代表和结构身份。

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

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

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

很晚了,但巧合的是,我偶然发现了同一个问题,准确的答案可以在这里找到:重要评论

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

当使用可枚举应用于列表时,使用谓词有效

  List<Product> products = new List<Product>
            {
                new Product {ProductID=1,Name="Kayak",Category="Watersports",Price=275m},
                new Product {ProductID=2,Name="Lifejacket", Category="Watersports",Price=48.95m},
                new Product {ProductID=3,Name="Soccer Ball", Category="Soccer",Price=19.50m},
                new Product {ProductID=4,Name="Corner Flag", Category="Soccer",Price=34.95m}
            };
Predicate<Product> predicate = x => x.ProductID==4;
            var query=Enumerable.FirstOrDefault(products,x=>predicate(x));
            if (query != null) { 
                output.WriteLine(query.Name);
                Assert.True(true);
            }