2016-02-24 2 views
2

Я хочу реализовать метод 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)); 
    } 
} 

Но это не работает из-за ошибки:

Error CS1929 'IEnumerable< int>' does not contain a definition for 'CastConvertible' and the best extension method overload 'ZEnumerable.CastConvertible< short>(IEnumerable< IConvertible>)' requires a receiver of type 'IEnumerable< IConvertible>'

Но 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, поэтому каждое значение помещается в квадрат, а затем генерирует исключение, потому что распаковка действительна только для точного начального типа.

+1

Почему вы не используя 'IEnumerable.Cast '? –

+0

Потому что он не работает в этом случае. Простой пример 'short [] shorts = new int [] {1,2,3} .Cast () .ToArray()' –

+0

См. [This] (http: // stackoverflow.com/questions/445471/puzzling-enumerable-cast-invalidcastexception) thread –

ответ

3

Отрывок из Covariance and Contravariance in Generics:

Variance applies only to reference types; if you specify a value type for a variant type parameter, that type parameter is invariant for the resulting constructed type.

Таким образом, ключевым моментом в вашем вопросе не встроенная, но значение типа.

Один из способов решить эту проблему, чтобы добавить еще один общий аргумент вашего метода расширения:

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

Почему не просто 'a.Cast (). ConvertTo ()'? – drowa

+0

@drowa OP хотел, чтобы метод отображался только для перечислений типов, которые реализуют 'IConvertible' (обратите внимание на ограничение' where T: IConvertible '). Хотя ваше предложение позволяет вызывать любое перечислимое и получать исключение во время выполнения, если вызвано неправильным типом. то есть разница заключается в компиляции и проверке времени выполнения. –

Смежные вопросы