10

请检查以下代码段:

public interface ICountable { }
public class Counter<T>
    where T : ICountable
{
    public int Count(IEnumerable<T> items)
    {
        return 0;
    }

    public int Count(T Item)
    {
        return 0;
    }
}

public class Counter
{
    public int Count<T>(IEnumerable<T> items)
        where T : ICountable
    {
        return 0;
    }

    public int Count<T>(T Item)
        where T : ICountable
    {
        return 0;
    }
}

两个版本的 Counter 仅在泛型参数的规范上有所不同。其中一个定义为泛型类型参数,另一个定义为泛型参数。两者都限制方法参数以实现 ICountable 接口。我将分别称它们为特定的和非特定的。

现在,我正在定义一个实现ICountable接口的类和一个实例集合:

public class CItem : ICountable { }
var countables = new List<CItem>();

然后,我想在集合上使用这两个 Counter 类。

var specific = new Counter<CItem>();
var nonspecific = new Counter();

specific.Count(countables);
nonspecific.Count(countables);

特定计数器识别可计数集合应属于签名int Count(IEnumerable),但非特定版本则不会。我得到错误:

类型“ System.Collections.Generic.List<CItem>”不能用作T泛型类型或方法“ ”中的类型参数“ Counter.Count<T>(T)”。没有从 List<CItem>' 到 的隐式引用转换ICountable

似乎非特定版本对集合使用了错误的签名。

为什么他们的行为不同?如何指定非特定版本以使其行为与其他版本相同?

注意:我知道这个例子是不现实的。但是,我在扩展方法非常复杂的场景中遇到了这个问题。为了简单起见,我使用这些类

提前致谢

4

3 回答 3

4

非特定类的问题是编译器在编译时不知道 T 类型,这就是为什么它不能为 method 选择正确的重载Count<T>()。但是,如果您设置泛型类型约束,编译器现在知道期望什么类型...

如果您使用签名注释掉您的方法,public int Count<T>(T Item)它将编译,因为它将使用具有正确签名的方法(即public int Count<T>(IEnumerable<T> items)

如果您通过将 List 强制转换为显式来帮助编译器推断类型,它也会编译和运行IEnumerable<CItem>

nonspecific.Count(countables as IEnumerable<CItem>);

看看简化的场景:

    static string A<T>(IEnumerable<T> collection)
    {
        return "method for ienumerable";
    }

    static string A<T>(T item)
    {
        return "method for single element";
    }

    static void Main(string[] args)
    {
        List<int> numbers = new List<int>() { 5, 3, 7 };
        Console.WriteLine(A(numbers));
    }

输出:“单元素方法”

于 2015-12-16T10:54:11.383 回答
2

如果我没记错(将尝试在规范中查找参考),则选择该T方法是因为它与类型完全匹配。

类型推断正确地识别出这两种泛型方法都适用,如Count<CItem>(IEnumerable<CItem> items)Count<List<CItem>>(List<CItem> items)。但是,第一个在重载决议中失败了,因为第二个更具体。约束仅在那之后才起作用,因此您会收到编译时错误。

如果您声明您的countables使用

IEnumerable<CItem> countables = new List<CItem>();

然后选择变成Count<CItem>(IEnumerable<CItem> items)并且Count<IEnumerable<CItem>>(IEnumerable<CItem> items)第一个赢得重载决议。

于 2015-12-16T11:08:55.307 回答
1

在我看来,编译器之所以认为您调用的是 Counter.Count(T) 而不是 Counter.Count< T >(IEnumerable< T >) 是因为后者需要从 List 转换为 IEnumerable。并且它的优先级低于使用以前的签名 Counter.Count(T),这会导致错误。

我认为最好将采用 IEnumerble 作为参数的方法名称更改为类似 CountAll 的名称。.NET 框架为 List.Remove 和 List.RemoveAll 做了一些事情。让你的代码更具体而不是让编译器来做所有决定是一个很好的做法。

于 2015-12-16T11:15:37.207 回答