Дженерики: кастинг и типы значений, почему это незаконно?

Почему это ошибка времени компиляции?

public TCastTo CastMe(TSource i) { return (TCastTo)i; } 

Ошибка:

annot преобразует тип ‘TSource’ в ‘TCastTo’

И почему это ошибка времени выполнения?

 public TCastTo CastMe(TSource i) { return (TCastTo)(object)i; } int a = 4; long b = CastMe(a); // InvalidCastException // this contrived example works int aa = 4; int bb = CastMe(aa); // this also works, the problem is limited to value types string s = "foo"; object o = CastMe(s); 

Я искал SO и Интернет для ответа на этот вопрос и нашел много объяснений по подобным родовым связанным вопросам кастинга, но я не могу найти ничего в этом конкретном простом случае.

Почему это ошибка времени компиляции?

Проблема в том, что каждая возможная комбинация типов значений имеет разные правила для того, что означает литье. Передача 64-битного двоичного кода в 16-битный int – это совершенно другой код от каста десятичного до плавания и т. Д. Количество возможностей огромно. Так что подумайте, как компилятор. Какой код должен компилировать для вашей программы?

Компилятор должен будет генерировать код, который снова запускает компилятор во время выполнения, проводит новый анализ типов и динамически испускает соответствующий код .

Похоже, что, возможно, больше работы и меньше производительности, чем вы ожидали получить от дженериков, поэтому мы просто запрещаем это. Если вы действительно хотите, чтобы компилятор снова запустился и выполнил анализ типов, используйте «dynamic» в C # 4; вот что он делает.

И почему это ошибка времени выполнения?

Та же самая причина.

Ячейка в ящике int может быть только unboxed для int (или int?) По той же причине, что и выше; если CLR попыталась сделать любое возможное преобразование из типа с коротким значением в любой другой возможный тип значения, то по существу он должен снова запустить компилятор во время выполнения . Это было бы неожиданно медленным.

Так почему же это не ошибка для ссылочных типов?

Поскольку каждое преобразование ссылочного типа является таким же, как и для любого другого преобразования ссылочного типа : вы запрашиваете объект, чтобы узнать, получен он из или идентичен требуемому типу. Если это не так, вы делаете исключение (если выполняете приведение) или результат null / false (если используются операторы «as / is»). Правила соответствуют для ссылочных типов таким образом, что они не относятся к типам значений. Помните, что ссылочные типы знают свой собственный тип . Типы значений нет; с типами значений переменная, использующая хранилище, является единственной вещью, которая знает семантику типа, которая применяется к этим битам . Типы значений содержат их значения и никакой дополнительной информации . Типы ссылок содержат их значения плюс множество дополнительных данных.

Для получения дополнительной информации см. Мою статью на эту тему:

http://ericlippert.com/2009/03/03/representation-and-identity/

C # использует один синтаксис для нескольких разных базовых операций:

  • вентиляционный
  • удрученный
  • заниматься боксом
  • распаковка
  • цифровое преобразование
  • пользовательское преобразование

В обобщенном контексте компилятор не знает, кто из них прав, и все они генерируют разные MSIL, поэтому он сворачивает.

Написав return (TCastTo)(object)i; вместо этого вы вынуждаете компилятор делать upcast для object , за которым следует downcast to TCastTo . Компилятор будет генерировать код, но если это неверный способ конвертировать типы, о которых идет речь, вы получите ошибку времени выполнения.


Образец кода:

 public static class DefaultConverter { private static Converter cached; static DefaultConverter() { ParameterExpression p = Expression.Parameter(typeof(TSource)); cached = Expression.Lambda(Expression.Convert(p, typeof(TCastTo), p).Compile(); } public static Converter Instance { return cached; } } public static class DefaultConverter { public static TOutput ConvertBen(TInput from) { return DefaultConverter.Instance.Invoke(from); } public static TOutput ConvertEric(dynamic from) { return from; } } 

Конечно, путь Эрика короче, но я думаю, что мой должен быть быстрее.

Ошибка компиляции вызвана тем, что TSource не может быть неявно применен к TCastTo. Эти два типа могут делиться веткой на дереве их наследования, но нет никакой гарантии. Если вы хотите вызывать только типы, которые совместно использовали предок, вам следует изменить подпись CastMe (), чтобы использовать тип предка вместо дженериков.

Пример ошибки времени выполнения избегает ошибки в вашем первом примере, сначала запустив TSource i в объект, из чего все объекты на C # получаются. В то время как компилятор не жалуется (поскольку объект -> что-то, что вытекает из него, может быть действительным), поведение кастинга через (синтаксис) переменной будет бросаться, если приведение недействительно. (Та же проблема, с которой компилятор не допустил в примере 1).

Другое решение, которое делает что-то похожее на то, что вы ищете …

  public static T2 CastTo(T input, Func convert) { return convert(input); } 

Вы бы назвали это так.

 int a = 314; long b = CastTo(a, i=>(long)i); 

Надеюсь, это поможет.

  • Как удалить элементы из общего списка во время итерации по нему?
  • Интерфейсы Java и типы возврата
  • Общий код C # и оператор Plus
  • как экземпляр List ?
  • Что означает «T» в C #?
  • Список readonly с частным набором
  • Почему C # (4.0) не допускает совпадения и контравариантность в типах универсального classа?
  • Почему вызов типа Generic типа Java 8 выбирает эту перегрузку?
  • Значение типа 'T' не может быть преобразовано в
  • Java generics: Collections.max () подпись и компаратор
  • Преобразование общего списка в строку CSV
  • Давайте будем гением компьютера.