内置类型的协方差

本文关键字:方差 置类型 内置 | 更新日期: 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"的接收器

但是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因此每个值都装箱,然后引发异常,因为取消装箱仅对确切的初始类型有效。

内置类型的协方差

自泛型中的协方差和逆变:

方差仅适用于引用类型;如果为变型类型参数指定值类型,则该类型参数对于生成的构造类型是不变的。

所以你问题的关键点不是内置的,而是的类型。

解决此问题的一种方法是向扩展方法添加另一个泛型参数:

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