Подтвердить что ты не робот

Почему кастинг дает CS0030, а "как" работает?

Предположим, что у меня есть общий метод:

T Foo(T x) {
    return x;
}

Пока все хорошо. Но я хочу сделать что-то особенное, если это Hashtable. (Я знаю, что это совершенно надуманный пример. Foo() тоже не очень интересный метод.).

if (typeof(T) == typeof(Hashtable)) {
    var h = ((Hashtable)x);  // CS0030: Cannot convert type 'T' to 'System.Collections.Hashtable'
}

штопать. Справедливости ради, однако, я не могу сказать, должно ли это быть законным С# или нет. Что ж, если я попытаюсь сделать это по-другому?

if (typeof(T) == typeof(Hashtable)) {
    var h = x as Hashtable;  // works (and no, h isn't null)
}

Это немного странно. Согласно MSDN, expression as Type (кроме оценки выражения дважды) совпадает с expression is type ? (type)expression : (type)null.

Что произойдет, если я попытаюсь использовать эквивалентное выражение из документов?

if (typeof(T) == typeof(Hashtable)) {
    var h = (x is Hashtable ? (Hashtable)x : (Hashtable)null);  // CS0030: Cannot convert type 'T' to 'System.Collections.Hashtable'
}

Единственное задокументированное различие между литьем и as, которое я вижу, это "оператор as выполняет только преобразования ссылок и преобразования бокса". Может быть, мне нужно сказать, что я использую ссылочный тип?

T Foo(T x) where T : class {
    var h = ((Hashtable)x);  // CS0030: Cannot convert type 'T' to 'System.Collections.Hashtable'
    return x;
}

Что происходит? Почему as работает нормально, в то время как кастинг даже не компилируется? Должна ли работа с кастом или работать с as или существует какая-то другая разница между литьем и as, которая не находится в этих документах MSDN, которые я нашел?

4b9b3361

Ответ 1

Ответ Бен в основном касается гвоздя на голове, но немного расширяется:

Проблема заключается в том, что у людей есть естественное ожидание того, что общий метод будет делать то же самое, что и эквивалентный не общий метод, если задавать типы во время компиляции. В вашем конкретном случае люди ожидали бы, что если T короткий, то (int)t должен сделать правильную вещь - превратить short в int. И (double)t должен превратить короткий в двойной. И если T байт, то (int)t должен повернуть байт в int, а (double)t должен повернуть байт в double... и теперь, возможно, вы начнете видеть проблему. Общий код, который нам нужно сгенерировать, в основном должен был бы снова запустить компилятор во время выполнения и провести полный анализ типов, а затем динамически генерировать код для преобразования, как ожидалось.

Это потенциально дорого; мы добавили эту функцию в С# 4, и если вы этого хотите, вы можете пометить объекты как типа "динамические", а небольшая усеченная версия компилятора снова запустится во время выполнения и сделает для вас логику преобразования.

Но это дорогое дело, как правило, не то, что люди хотят.

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

Вот почему "как" разрешено в общем коде, но отливки не являются.

Все, что сказал: вы почти наверняка делаете это неправильно. Если вам нужно выполнить тест типа в общем коде, ваш код не является общим. Это действительно плохой запах кода.

Ответ 2

Оператор трансляции в С# может:

  • коробка/распаковывать
  • вентиляционный/опущенная
  • вызов пользовательского оператора преобразования

as Hashtable всегда означает вторую.

Исключив типы значений с ограничением, вы выбили вариант 1, но он все еще неоднозначен.


Вот два "лучших" подхода, которые оба работают:

Hashtable h = x as Hashtable;
if (h != null) {
    ...
}

или

if (x is Hashtable) {
    Hashtable h = (Hashtable)(object)x;
    ...
}

Сначала требуется только один тест типа, поэтому он очень эффективен. И оптимизатор JIT распознает второй, и рассматривает его как первый (по крайней мере, имея дело с не-генерическими типами, я не уверен в этом конкретном случае.)

Ответ 3

"Компилятор С# позволяет вам недвусмысленно вводить параметры типового типа для Object или для типов с ограничениями, как показано в блоке кода 5. Такое неявное литье является безопасным по типу, поскольку любая несовместимость обнаруживается во время компиляции."

См. раздел "Общие и кастинг": http://msdn.microsoft.com/en-us/library/ms379564(v=vs.80).aspx#csharp_generics_topic5