Инициализаторы объектов в блоке using

в 19:29, , рубрики: .net, метки:

Инициализаторы объектов (Object Initializers) – это полезная возможность языка C#, которая позволяет инициализировать необходимые свойства объекта прямо во время его создания. Поскольку синтаксически эта «фича» очень близка к инициализации объекта с передачей параметров через конструктор, многие разработчики начинают забивать на принципы ООП (в частности на понятие инварианта) и использовать ее, где только можно.

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

// position передается извне или настраиватся каким-то образом
long position = -1;
using (var file = new FileStream("d:\1.txt", FileMode.Append)
                        {
                            // Мы точно знаем, что нужные данные расположены
                            // с некоторым сдвигом!
                            Position = position
                        })
{
    // Делаем чего-то с файлом
}

В данном фрагменте внутри директивы using создается ресурс (файл) и устанавливается одно из его свойств (Position) с помощью инициализатора объекта. При этом самое главное в этом коде то, что setter этого свойства может генерировать исключение.

В отличие от языка C++ в .NET-ах мы довольно редко сталкиваемся с проблемами безопасностью исключений, но это один из тех редких случаев, когда код является небезопасным с точки зрения исключений.

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

class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}
 

// ...
var person = new Person {Name = "Jonh", Age = 42};

Временная переменная является необходимым условием «атомарности» инициализации, без которой сторонний код (например, из другого потока) смог бы получить ссылку на объект в промежуточном состоянии. Кроме того, отсутствие временной переменной сделало бы код еще менее безопасным с точки зрения исключений, ведь тогда генерация исключения из setter-а свойства привело бы частичной инициализации переменной:

var tmp = new Person();
tmp.Name = "Jonh";
tmp.Age = 42;
var person = tmp;

В этом случае, если сеттер одного из свойств упадет с исключением, то поле _person будет уже проинициализировано, но не до конца, что нарушило бы «атомарность» инициализатора объектов, такую привычную по использованию конструкторов.
Однако, хотя временная переменная решает ряд проблем, этого не происходит в случае использования инициализатора объектов внутри директивы using. Как вы знаете, директива using разворачивается в такой код:

// _person - это поле класса и, предположим, компилятор 
// разворачивал бы инициализатор «по простому»
var _person = new Person();
_person.Name = "Jonh";
_person.Age = 42;

Теперь, если сложить 2 и 2, то мы получим, что наш исходный пример разворачивается в следующее:

var file = new FileStream("d:\1.txt", FileMode.OpenOrCreate);
try
{}
finally
{
    if (file != null)
        ((IDisposable)file).Dispose();
}

И это означает, что если свойство, инициализируемое с помощью инициализатора объекта, упадет с исключением, метод Dispose нашего объекта вызван не будет.

Заключение

Инициализаторы объектов – это полезная возможность, но ее нужно еще и уметь готовить использовать. Хотя синтаксически (и семантически, кстати, тоже) эта возможность очень близка к вызову конструктора, но, к сожалению, они не всегда эквиваленты. Что касается принципов ООП, то тут вам самим решать, когда использовать инициализатор объектов, а когда нет, но если речь заходит о безопасности исключений и управлении ресурсами, то тут однозначно нужно понимать, как устроена эта возможность и что из этого следует.

Язык C# отличается довольно предсказуемым поведением в большинстве случаев (хотя есть и исключения, типа тонкостей с изменяемыми значимыми типами), но смешивание инициализаторов объектов с блоком using, интуитивностью не отличается и желательно понимать, как именно устроена эта комбинация, чтобы не отсрелить себе ногу по неосторожности.

Автор: SergeyT

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js