среда, 8 октября 2008 г.

Специализация типа для null

Как известно, литерал "null" в C# может быть неявно преобразован в ссылочный или nullable - тип. В любой ссылочный. Что, порой доставляет неудобства: допустим, у нас есть пара методов:

void Add(string text) { /* … */ }
void Add(Control content) { /* … */ }

и нам необходимо вызвать первый из них, передав в него значение null:

Add(null);

Так как null здесь может совершенно равноправно быть преобразован и в string и в Control, подобный вызов приведёт к ошибке компиляции:

error CS0121: The call is ambiguous between the following methods or properties: '…Add(string)' and '…Add(…Control)'

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

Add((string)null);

то есть используя тот же синтаксис, какой используется для приведения типа.

Приведение типа - операция, происходящая во время выполнения программы и может закончится возникновением исключительной ситуации, поэтому к приведению типов многие програмисты относятся осторожно.

А вот "приведение" null к какому-либо типу в худшем случае закончится ошибкой компиляции и не требует такого осторожного обращения. Некоторым даже кажется недостатком языка то, что различные операции изменения типа (результат которых может или не может быть проверен компилятором) синтаксически выглядят одинаково (вспомним Casting Operators из C++).

К радости таких вот педантов (к которым я и себя отношу), в C# версии 2.0 появилось

default-value-expression:
default (type)

"Соль" данного выражения в том, что во время выполнения оно имеет значение null (для reference-типа), а его тип именно тот, что указан. С помощью default-value-expression вызов метода можно переписать так:

Add(default(string));

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

Так же, default-value-expression поможет нам и в такой ситуации:

int? x = flag ? 10 : null;

error CS0173: Type of conditional expression cannot be determined because there is no implicit conversion between 'int' and '<null>'

Компилятор не может вывести тип выражения после оператора присваивания если ему (компилятору) не помочь так:

var x = flag ? 10 : (int?)null;

так

var x = flag ? 10 : new int?();

или так:

var x = flag ? 10 : default(int?);

Несмотря на то, что последний вариант самый длинный из предложенных, мне он нравится больше всего, ибо и приведение типа, и создание экземпляра объекта синтаксически выглядят как выполнение какой-то операции, хотя на самом деле в данном месте достаточно типизированного константного выражения.

И ещё одно место, где использование default-value-expression мне кажется оправданным: объявление и инициализация значением по-умолчанию локальных переменных. Например, мне кажется нелогичным, что в таком вот примере:

var my_integer = 0;
var my_string = null;

error CS0815: Cannot assign <null> to an implicitly-typed local variable

в первом объявлении мы можем использовать var, а во-втором нет и нам приходится явно указывать тип переменной:

var my_integer = 0;
string my_string = null;

Куда как однообразнее выглядит

var my_integer = 0;
var my_string = default(string);

Вот, пока что, и всё о простом, но интересном и полезном, на мой взгляд, средстве языка C#. Надеюсь, этот блог будет жить более активной жизнью, чем мой предыдущий на gotdotnet :о) Редактирование тут кажется более удобным (что является основным стопором), хотя LiveWriter мне прикрутить пока не удалось.

2 комментария:

  1. Отличный прием! Возьму на вооружение. Для тернарного оператора очень актуально.

    ОтветитьУдалить
  2. С инициализацией локальных var переменных ты хорошо придумал, давно с этим мучаюсь. ))

    ОтветитьУдалить