我想实现Cast
方法,因为我有很多丑陋的source.Select(x => type(x)).ToArray()
。所以我写了一个简单的扩展名:
public static IEnumerable<TResult> CastConvertible<TResult>(this IEnumerable<IConvertible> source)
{
foreach (var value in source)
{
yield return (TResult) Convert.ChangeType(value, typeof (TResult));
}
}
但由于错误,它无效:
错误CS1929'IEnumerable&lt; INT&GT;”不包含的定义 'CastConvertible'和最好的扩展方法重载 “ZEnumerable.CastConvertible&LT;简短&gt;(IEnumerable&lt; IConvertible&gt;)' 需要一个'IEnumerable&lt;类型的接收器IConvertible&GT;'
但int
为IConvertible
,而我们知道IEnumerable<out T>
是协变的,因此IEnumerable<DerivedType>
可以转换为IEnumerable<BaseType>
。
以下是一个例子:
int a = 10;
int[] b = {a};
IConvertible aa = a;
IEnumerable<IConvertible> bb = b;
所以我应该删除where
约束才能使用此方法,但在这种情况下,我将失去编译时检查,可以转换类型。
为什么协方差在这种情况下不起作用?
我没有使用Enumerable.Cast<T>
,因为它不适用于内置类型。例如short[] shorts = new int[] {1, 2, 3}.Cast<short>().ToArray();
将抛出异常,因为Cast
方法使用内部非泛型IEnumerable
,因此每个值都被装箱然后抛出异常,因为取消装箱仅对精确的初始类型有效。
答案 0 :(得分:3)
摘自Covariance and Contravariance in Generics:
差异仅适用于参考类型;如果为变量类型参数指定值类型,则该类型参数对于生成的构造类型是不变的。
因此,问题的关键点不是内置,而是值类型。
解决此问题的一种方法是在扩展方法中添加另一个通用参数:
public static IEnumerable<T, TResult> CastConverible<TResult>(this IEnumerable<T> source)
where T : IConvertible
但它不会那么有用,因为调用者需要指定两种泛型类型,而不仅仅是TResult
。
另一种方法是在非通用IEnumerable
上定义您的扩展方法(类似于Cast
)
public static IEnumerable<TResult> CastConverible<TResult>(this IEnumerable source)
但是这样你就无法将它约束到IConvertible
个元素。
我看到的最佳选择是用两种新的扩展方法替换你的方法:
public static IEnumerable<IConvertible> AsConvertible<T>(this IEnumerable<T> source)
where T : IConvertible
{
return source as IEnumerable<IConvertible> ?? source.Select(item => (IConvertible)item);
}
public static IEnumerable<TResult> ConvertTo<TResult>(this IEnumerable<IConvertible> source)
{
return source as IEnumerable<TResult> ??
source.Select(item => (TResult)Convert.ChangeType(item, typeof(TResult)));
}
样本用法不会那么简洁,但仍然流利:
int[] a = { 1, 2, 3 };
var b = a.AsConvertible().ConvertTo<byte>();