Почему литье дает CS0030, а "как" работает? - PullRequest
19 голосов
/ 21 сентября 2011

Предположим, у меня есть универсальный метод:

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'
}

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

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, которые я нашел?

Ответы [ 3 ]

17 голосов
/ 21 сентября 2011

Ответ Бена в основном бьет ногтем по голове, но вкратце остановимся на этом:

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

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

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

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

Вот почему «as» разрешено в общем коде, но приведение не разрешено.

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

7 голосов
/ 21 сентября 2011

Оператор приведения в C # может:

  • box / unbox
  • upcast / downcast
  • вызвать пользовательский оператор преобразования

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

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


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

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

или

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

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

4 голосов
/ 21 сентября 2011

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...