Object Initializers (Object Initializers) is a useful feature of the C # language, which allows you to initialize the necessary properties of an object right at the time of its creation. Since, syntactically, this “feature” is very close to initializing an object with passing parameters through a constructor, many developers begin to hammer on OOP principles (in particular, on the concept of an invariant) and use it wherever possible.
But even if we don’t go over to holivars and obscure terms, let's consider a small example and consider whether it can lead to problems or not:
')
In this fragment, a resource (file) is created inside the
using directive and one of its properties (
Position ) is set using the object initializer. In this case, the most important thing in this code is that the
setter of this property can generate an exception.
Unlike C ++ in .NET, we rarely encounter exceptions security problems, but this is one of those rare cases where code
is unsafe from the point of view of exceptions .
To understand this, let's see how the object's initializer is implemented by the compiler:
class Person { public string Name { get; set; } public int Age { get; set; } }
At first glance, it may seem that the object's initializer is nothing more than a call to the constructor and then change its properties. And, in fact, the way it is, with only a small clarification:
var tmp = new Person(); tmp.Name = "Jonh"; tmp.Age = 42; var person = tmp;
A temporary variable is a necessary condition for the "atomicity" of initialization, without which third-party code (for example, from another thread) could get a reference to an object in an intermediate state. In addition, the absence of a temporary variable would make the code even less safe from the point of view of exceptions, because then generating an exception from the setter property would result in partial initialization of the variable:
var tmp = new Person(); tmp.Name = "John"; tmp.Age = 42; var person = tmp;
In this case, if the setter of one of the properties falls with the exception, then the
_ person field will already be initialized, but not completely, which would violate the "atomicity" of the object initializer, which is so familiar with the use of constructors.
However, although the temporary variable solves a number of problems, this does not happen when using an object initializer within the
using directive. As you know, the
using directive expands to this code:
var file = new FileStream("d:\\1.txt", FileMode.OpenOrCreate); try {} finally { if (file != null) ((IDisposable)file).Dispose(); }
Now, if we add up 2 and 2, then we get that our original example turns into the following:
long position = -1; var tmpFile = new FileStream("d:\\1.txt", FileMode.OpenOrCreate);
And this means that if a property initialized with an object's initializer falls with an exception, the
Dispose method of our object will not be called.
ConclusionObject initializers are a useful feature, but you need to be able
to use it as well. Although syntactically (and semantically, by the way, too) this possibility is very close to calling the constructor, but, unfortunately, they are not always equivalent. As for the principles of OOP, then it is up to you to decide when to use an object initializer, and when not, but when it comes to exceptions security and resource management, then you definitely need to understand how this feature works and what follows from this.
C # language has a rather predictable behavior in most cases (although there are exceptions, such as subtleties with
variable significant types ), but mixing object initializers with a
using block doesn’t differ by intuitiveness and it is desirable to understand exactly how this combination works so as not to shoot yourself carelessness.