内置类型的协方差

时间:2016-02-24 08:43:19

标签: c# .net

我想实现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;'

intIConvertible,而我们知道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,因此每个值都被装箱然后抛出异常,因为取消装箱仅对精确的初始类型有效。

1 个答案:

答案 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>();