内置类型的协方差
本文关键字:方差 置类型 内置 | 更新日期: 2023-09-27 18:35:55
我想实现一个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
"不包含 "铸造敞篷车"和最佳扩展方法重载 'ZEnumerable.CastConvertible (IEnumerable )' 需要类型为"IEnumerable "的接收器
但是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
因此每个值都装箱,然后引发异常,因为取消装箱仅对确切的初始类型有效。
自泛型中的协方差和逆变:
方差仅适用于引用类型;如果为变型类型参数指定值类型,则该类型参数对于生成的构造类型是不变的。
所以你问题的关键点不是内置的,而是值的类型。
解决此问题的一种方法是向扩展方法添加另一个泛型参数:
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>();