泛型方法以不同于泛型类型的方式处理 IEnumerable
Generic method handles IEnumerable differently than generic type
请检查以下代码段:
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 接口。我将分别称它们为 specific 和 non specific。
现在,我正在定义一个实现 ICountable 接口的 class,以及一个实例集合:
public class CItem : ICountable { }
var countables = new List<CItem>();
然后,我想在集合中使用两个计数器 classes。
var specific = new Counter<CItem>();
var nonspecific = new Counter();
specific.Count(countables);
nonspecific.Count(countables);
特定计数器识别countables集合应该落入签名int Count(IEnumerable),但非特定版本确实不是。我收到错误:
The type 'System.Collections.Generic.List<CItem>
' cannot be used as
type parameter 'T
' in the generic type or method
'Counter.Count<T>(T)
'. There is no implicit reference conversion from
List<CItem>
' to ICountable
.
似乎非特定版本使用了错误的集合签名。
为什么他们的行为不同?
如何指定非特定版本以使其行为与其他版本相同?
注意:我知道这个例子是不现实的。但是,我在使用扩展方法的相当复杂的场景中遇到了这个问题。为了简单起见,我使用这些 classes
提前致谢
非特定 class 的问题是编译器在编译时不知道类型 T,这就是它无法 select 纠正方法 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));
}
输出:"method for single element"
如果我没记错的话(将尝试在规范中找到参考),选择 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)
,第一个赢得重载决议。
在我看来,编译器认为你调用的是 Counter.Count(T) 而不是 Counter.Count< T >(IEnumerable< T >) 的原因是因为后者需要一个从 List 到 IEnumerable 的转换。这比使用前一个签名 Counter.Count(T) 的优先级低,这会导致错误。
我认为最好将以 IEnumerble 作为参数的方法名称更改为类似 CountAll 的名称。 .NET 框架为 List.Remove 和 List.RemoveAll 做的一些事情。使您的代码更具体而不是让编译器做所有决定是一种很好的做法。
请检查以下代码段:
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 接口。我将分别称它们为 specific 和 non specific。
现在,我正在定义一个实现 ICountable 接口的 class,以及一个实例集合:
public class CItem : ICountable { }
var countables = new List<CItem>();
然后,我想在集合中使用两个计数器 classes。
var specific = new Counter<CItem>();
var nonspecific = new Counter();
specific.Count(countables);
nonspecific.Count(countables);
特定计数器识别countables集合应该落入签名int Count(IEnumerable),但非特定版本确实不是。我收到错误:
The type '
System.Collections.Generic.List<CItem>
' cannot be used as type parameter 'T
' in the generic type or method 'Counter.Count<T>(T)
'. There is no implicit reference conversion fromList<CItem>
' toICountable
.
似乎非特定版本使用了错误的集合签名。
为什么他们的行为不同? 如何指定非特定版本以使其行为与其他版本相同?
注意:我知道这个例子是不现实的。但是,我在使用扩展方法的相当复杂的场景中遇到了这个问题。为了简单起见,我使用这些 classes
提前致谢
非特定 class 的问题是编译器在编译时不知道类型 T,这就是它无法 select 纠正方法 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));
}
输出:"method for single element"
如果我没记错的话(将尝试在规范中找到参考),选择 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)
,第一个赢得重载决议。
在我看来,编译器认为你调用的是 Counter.Count(T) 而不是 Counter.Count< T >(IEnumerable< T >) 的原因是因为后者需要一个从 List 到 IEnumerable 的转换。这比使用前一个签名 Counter.Count(T) 的优先级低,这会导致错误。
我认为最好将以 IEnumerble 作为参数的方法名称更改为类似 CountAll 的名称。 .NET 框架为 List.Remove 和 List.RemoveAll 做的一些事情。使您的代码更具体而不是让编译器做所有决定是一种很好的做法。