Nuget package to implement the Lazy Initialization pattern in a thread-safe and efficient manner.
public class SampleService
{
private readonly Func<ExpensiveObject> _expensiveLoad = LazyProperty.Create(() => new ExpensiveObject());
public ExpensiveObject ExpensiveLoad => _expensiveLoad();
public void DoWork(int n) => ExpensiveLoad.Move(n*10 - 100, n*10 + 100);
}SampleService depends on ExpensiveObject to do some work.
The creation of a ExpensiveObject is a computational intensive task.
The SampleService's ExpensiveLoad property will return a new ExpensiveObject when read the first time. Moreover, It will cache this result in a thread-safe and efficient manner.
Follow the steps from the LazyPropertyHelper Nuget Package.
None
Yes. Sample project.
Yes.
Developers that have written code similar to this one:
public class MyServiceNaive
{
private ExpensiveObject expensiveLoad;
public ExpensiveObject ExpensiveLoad
{
get
{
if (expensiveLoad == null)
{
expensiveLoad = new ExpensiveObject();
}
return expensiveLoad;
}
}
//more code
}The ExpensiveLoad property is not thread-safe. It can be subject to weird race conditions.
The following code is thread-safe. However, it is inefficient because it acquires a lock whenever ExpensiveLoad is read.
The LazyPropertyHelper nuget prevents unnecessary locks.
public class MyLockedService
{
private object criticalSection = new object();
private ExpensiveObject expensiveLoad;
public ExpensiveObject ExpensiveLoad
{
get
{
lock (criticalSection)
{
if (expensiveLoad == null)
{
expensiveLoad = new ExpensiveObject();
}
}
return expensiveLoad;
}
}
}LazyPropertyHelper leverages the advantages of lambdas and functional programming to cache the result of an expensive computation. The computation is executed only once in a thread-safe context. Subsequent reads don't require a lock.
Here's the code where all of this takes place. The important piece is the CalculateAndCacheExpensiveComputation method that replaces the _expensiveComputationReader with a lambda that always return the cached value from the _cachedResult field.
Testing concurrent code is hard [citation needed]. We didn't even bother to simulate concurrency problems in unit tests.
However, we wrote dozens of unit tests to make sure that the LazyPropertyHelper behaves as expected.
I did not create this pattern. I was inspired by a similar implementation from this great book about functional programming in Java.
Isn't this the same as Lazy<T>?
Lazy<T> solves many of the problems around lazy initialization. You could even argue how much richer it is.
However, Lazy<T> adopts an all or nothing approach to locking. It can be configured to never lock or to always lock. These extremes can be highly inefficient or dangerous in certain situations.
LazyPropertyHelper takes a simpler approach with no compromises on thread-safety and performance.
No. There are several unit tests to cover the destruction of the cached objects.
The result of the expensive computation can be explicitly disposed from the Dispose method of the class with the lazy property.