вторник, 21 октября 2008 г.

#endregion-ы

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

Служат регионы для возможности отделения блока кода внутри региона от остального кода по-соседству.

Используютя регионы, зачастую, именно так, как показано в примере из MSDN:

// preprocessor_region.cs
#region MyClass definition

public class MyClass
{
  public static void Main()
  {
  }
}

#endregion

то есть начинаются как:

#region Какое-либо описание ("заголовок")

а заканчиваются:

#endregion

чему во многом этому способствует "стандартный" сниппет (находящийся в "Program Files\Microsoft Visual Studio 9.0\VC#\Snippets\1033\Visual C#\pp_region.snippet"), позволяющий в несколько нажатий клавиш вставить в редактор блок #region#endregion и ввести лишь "заголовок".

Директива #endregion, кажется, нужна лишь для того, что бы обозначить окончание региона и больше проку от неё нет. Но … это только на первый взгляд :о) она так же, как и #region может содержать информативное текстовое описание, а, значит, подсказывать, что же за код находится выше неё в редакторе и, часто, это очень полезно если "размер" региона больше одного экрана или просто не хочется высоко поднимать глаза, что бы увидеть, что же написано в заголовке региона. Главное, получить это можно практически бесплатно, если для вставки регионов вы пользуетесь code-snippets. Достаточно открыть в редакторе (хоть в той же MSVS) указанный выше файл pp_region.snippet и заменить

<Code Language="csharp">
   <![CDATA[#region $name$
 $selected$$end$
#endregion
]]>
</Code>

на

<Code Language="csharp">
   <![CDATA[#region $name$
 $selected$$end$
#endregion $name$
]]>
</Code>

Теперь у вас будут более информативные и даже более стройные регионы:

// preprocessor_region.cs
#region MyClass definition

public class MyClass
{
  public static void Main()
  {
  }
}

#endregion MyClass definition

Не забывайте: человек может читать или просто просматривать код не только "сверху вниз", но и "снизу вверх", и в таком случае указанный у #endregion коментарий послужит на пользу.

среда, 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 мне прикрутить пока не удалось.