通用方法处理IEnumerable与泛型类型不同

时间:2015-12-16 10:44:45

标签: c# .net generics generic-method generic-type-argument

请检查以下代码段:

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);

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

  

类型&#39; System.Collections.Generic.List<CItem>&#39;不能用作   类型参数&#39; T&#39;在泛型类型或方法中   &#39; Counter.Count<T>(T)&#39 ;.没有隐式引用转换   List<CItem>&#39;到ICountable

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

他们为什么表现不一样? 如何指定非特定版本以使其行为与另一个版本相同?

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

提前致谢

3 个答案:

答案 0 :(得分:4)

非特定类的问题是编译器在编译时不知道类型T,这就是为什么它不能为方法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));
    }

输出:“单个元素的方法”

答案 1 :(得分: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),第一个选择赢得重载决策。

答案 2 :(得分:1)

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

我认为将IEnumerble作为参数更改为CountAll之类的方法名称会更好。 .NET框架为List.Remove和List.RemoveAll做的一些事情。最好使代码更具体,而不是让编译器做出所有决定。