为什么编译器会选择使用IEnumerable而不是IEnumerable<T>进行重载?

5

考虑以下两个扩展方法:

using System;
using System.Collections.Generic;
using System.Linq;

public static class Extensions
{
    public static bool Contains(this IEnumerable self, object obj)
    {
        foreach (object o in self)
        {
            if (Object.Equals(o, obj))
            {
                return true;
            }
        }
        return false;
    }

    public static bool ContainsEither<T>(this IEnumerable<T> self, T arg1, T arg2)
    {
        return self.Contains(arg1) || self.Contains(arg2);
    }
}

当我写了第二个方法时,我的意图是调用通用的LINQ Enumerable.Contains<T>方法(类型参数从使用中推断)。然而,我发现它实际上调用了第一个方法(我的自定义Contains()扩展方法)。当我注释掉我的Contains()方法时,第二个方法编译良好,使用Enumerable.Contains<T>()方法。
我的问题是,为什么编译器会选择带有非泛型IEnumerable参数的Contains()方法,而不是带有IEnumerable<T>参数的Enumerable.Contains<T>()方法?我希望它选择Enumerable.Contains<T>(),因为IEnumerable<T>IEnumerable更派生。

泛型更具体。 - Daniel A. White
一般来说,在这种情况下,“为什么”这个问题通常很有趣,但更重要的是“我该如何使它不……”。 - Lasse V. Karlsen
@LasseV.Karlsen 实际上只是关于为什么。我本可以写Enumerable.Contains(...)或Contains<T>(...)。 - Mr Anderson
1个回答

20
我的问题是,为什么编译器会选择带有非泛型 IEnumerable 参数的 Contains() 方法,而不是带有 IEnumerable<T> 参数的 Enumerable.Contains<T>() 方法?
因为它在包含调用方法的相同命名空间中被找到。在该命名空间内声明的类型实际上优先于导入命名空间中声明的类型。
来自 C# 5 规范,第7.6.5.2节:
搜索C的过程如下:
- 从最近的封闭命名空间声明开始,继续每个封闭命名空间声明,以包含编译单元结束,依次尝试找到候选扩展方法集:
- 如果给定的命名空间或编译单元直接包含具有合适扩展方法Mj的非泛型类型声明Ci,则该扩展方法集是候选扩展方法集。
- 如果使用命名空间指令导入的命名空间在给定的命名空间或编译单元内直接包含具有合适扩展方法Mj的非泛型类型声明Ci,则该扩展方法集为候选扩展方法集。
- 如果在任何封闭命名空间声明或编译单元中未找到候选扩展方法集,则会发生编译时错误。

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