To demonstrate examples, I will use the Test class, which has a BlobData property that returns an object of the Blob type, which, according to legend, is created quite slowly, and it was decided to create it lazily.
class Test
{
public Blob BlobData
{
get
{
return new Blob();
}
}
}
Checking for null
The simplest option, available from the first versions of the language, is to create an uninitialized variable and test it for null before returning. If the variable is null, create an object and assign it to this variable, and then return it. On repeated access, the object will already be created and we will return it immediately.
class Test
{
private Blob _blob = null;
public Blob BlobData
{
get
{
if (_blob == null)
{
_blob = new Blob();
}
return _blob;
}
}
}
An object of type Blob is created here when the property is first accessed. Or it is not created, if for some reason the program did not need it in this session.
Ternary operator?:
C # has a ternary operator that allows you to test a condition and, if it is true, return one value, and if it is false, another. We can use it to shorten and simplify the code a little.
class Test
{
private Blob _blob = null;
public Blob BlobData
{
get
{
return _blob == null
? _blob = new Blob()
: _blob;
}
}
}
The essence remains the same. If the object is not initialized, then initialize and return. If it is already initialized, then we just return it immediately.
is null
Situations are different and we, for example, may encounter one in which the Blob class has an overloaded == operator. To do this, we may probably need to do the is null check instead of == null. Available in the latest versions of the language.
return _blob is null
? _blob = new Blob()
: _blob;
But this is so, a small digression.
The null-coalescing operator ??
The binary operator ?? will help us to simplify the code even more.
The essence of his work is as follows. If the first operand is not null, then it is returned. If the first operand is null, the second is returned.
class Test
{
private Blob _blob = null;
public Blob BlobData
{
get
{
return _blob ?? (_blob = new Blob());
}
}
}
The second operand had to be enclosed in parentheses due to the priority of the operations.
Operator ?? =
C # 8 introduces a null-coalescing assignment operator that looks like this ?? =
How it works is as follows. If the first operand is not null, then it is simply returned. If the first operand is null, then the value of the second is assigned to it and this value is returned.
class Test
{
private Blob _blob = null;
public Blob BlobData
{
get
{
return _blob ??= new Blob();
}
}
}
This allowed us to shorten the code a little more.
Streams
If there is a possibility that several threads can access a given resource at once, we should make it thread safe. Otherwise, a situation may occur that, for example, both threads will check the object for null, the result will be false, and then two Blob objects will be created, loading the system twice as much as we wanted, and in addition, one of these objects will be saved, and the second will be lost.
class Test
{
private readonly object _lock = new object();
private Blob _blob = null;
public Blob BlobData
{
get
{
lock (_lock)
{
return _blob ?? (_blob = new Blob());
}
}
}
}
The lock statement acquires a mutually exclusive lock on the specified object before executing certain statements, and then releases the lock. It is equivalent to using the System.Threading.Monitor.Enter (..., ...);
Lazy <T>
.NET 4.0 introduced the Lazy class to hide all this dirty work from our eyes. Now we can only leave a local variable of type Lazy. When accessing its Value property, we get an object of the Blob class. If the object was created earlier, it will return immediately, if not, it will be created first.
class Test
{
private readonly Lazy<Blob> _lazy = new Lazy<Blob>();
public Blob BlobData
{
get
{
return _lazy.Value;
}
}
}
Since the Blob class has a parameterless constructor, Lazy can create it at the right time without any questions. If we need to perform some additional actions during the creation of the Blob object, the constructor of the Lazy class can take a reference to the Func <T>
private Lazy<Blob> _lazy = new Lazy<Blob>(() => new Blob());
In addition, in the second parameter of the constructor, we can indicate whether we need thread safety (the same lock).
Property
Now let's shorten the notation of the readonly property, since modern C # allows you to do this nicely. In the end, it all looks like this:
class Test
{
private readonly Lazy<Blob> _lazy = new Lazy<Blob>();
public Blob BlobData => _lazy.Value;
}
LazyInitializer
There is also an option not to wrap the class in a Lazy wrapper, but instead use the LazyInitializer functionality. This class has one static method EnsureInitialized with a bunch of overloads that allow you to create anything, including thread safety and write custom code to create an object, but the main essence of which is as follows. Check if the object is not initialized. If not, then initialize. Return an object. Using this class, we can rewrite our code like this:
class Test
{
private Blob _blob;
public Blob BlobData => LazyInitializer.EnsureInitialized(ref _blob);
}
That's all. Thank you for attention.