Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
Мне очень часто задают вопрос о логике преобразования типов в языке C#, что, в общем-то, не удивительно. Преобразования типов является распространенной операцией и соответствующие правила довольно запутанные. Вот фрагмент кода, о котором у меня недавно спросили; я упростил его ради ясности:
class C<T> {}
class D
{
public static C<U> M<U>(C<bool> c)
{
return something;
}
}
public static class X
{
public static V Cast<V>(object obj) { return (V)obj; }
}
Где «something» может представлять собой один из трех вариантов:
Вариант 1: (C<U>)c
Вариант 2: X.Cast<C<U>>(c);
Вариант 3: (C<U>)(object)c
Первый вариант не компилируется. Второй и третий варианты успешно компилируются но «падают» во время выполнения, если U не является типом bool.
Вопрос: Почему первый вариант не компилируется?
Поскольку компилятор знает, что данное преобразование может выполняться успешно, только если U является bool, а U может быть чем угодно! Компилятор считает, что в большинстве случаев U не будет типом bool, поэтому данный код наверняка является ошибкой, о чем компилятор и говорит.
Вопрос: Так почему тогда вторая версия компилируется?
Потому что компилятор не знает, что метод X.Cast<V> собирается преобразовывать аргумент к типу V! Все что видит компилятор, так это вызов метода, принимающего объект (и вы передаете объект), работа компилятора на этом заканчивается. С точки зрения компилятора вызов метода является «черным ящиком», и компилятор не заглядывает внутрь метода, чтобы определить, завершится ли неудачно этот метод при заданных входных данных. Это «преобразование» с точки зрения компилятора не является преобразованием, это просто вызов метода.
Вопрос: А как насчет третьего варианта? Почему он компилируется, в отличие от первого варианта?
На самом деле, третий вариант аналогичен второму; все, что мы делаем, это встраиваем вызов метода X.Cast<V>, добавляя при этом преобразование к типу object! Такое преобразование является корректным.
Вопрос: во втором и третьем случае, преобразование компилируется, поскольку в середине происходит конвертация к типу object?
Совершенно верно. Правило следующее: если существует преобразование типа S к объекту, тогда существует явное преобразование объекта к типу S. (*)
Преобразование к объекту перед «агрессивным» преобразованием говорит компилятору «забудь, пожалуйста, информацию времени компиляцию о типе преобразуемого объекта». В третьем варианте мы делаем это явно; во втором варианте мы делаем это скрытно, путем неявного преобразования к объекту передаваемого аргумента во время его преобразования к типу параметра.
Вопрос: это объясняет, почему проверка типов времени компиляции нормально не работает с LINQ выражениями?
Да! Можно подумать, что компилятор запретит глупости, типа:
from bool b in new int[] { 123, 345 } select b.ToString();
поскольку не существует преобразования из int к bool, так как же переменная диапазона b может принимать значения массива? Тем не менее, данный код компилируется, поскольку компилятор преобразует этот код к:
(new int[] { 123, 345 }).Cast<bool>().Select(b=>b.ToString())
и компилятор не имеет ни малейшего понятия о том, что последовательность целых чисел передается методу расширения Cast<bool>, который завершится с ошибкой во время выполнения. Этот метод является черным ящиком. Мы с вами знаем, что будет преобразование типов, которое завершится с ошибкой, но компилятор не знает этого.
Кстати, далеко не факт, что мы с вами тоже об этом знаем; возможно, мы используем некоторую библиотеку, отличную от поставщика запросов (query provider) LINQ-to-objects, который знает о возможности преобразования между типами, обычно запрещенными компилятором языка C#. На самом деле, это точка расширения языка, которая прячется за недостатком компилятора: так что это фича, а не баг!
(*) Обратите внимание, что я не сказал: «существует явное преобразование объекта к любому типу», потому что это не так. Можете ли вы назвать тип S, который не преобразовывается к объекту?
Comments
- Anonymous
September 03, 2012
Полезное. Спасибо!