What is a Lazy Injectable Property in C#?
The following is an example of a lazy injectable. If you are not working on legacy code without Dependency Injection (DI) and Inversion of Control (IoC) with constructor injection, then this article is likely not relevant to you.
/// <summary>This is an example of a Lazy Injectable that will eventually be replaced with constructor injection in the future.</summary> internal IMyNewClass MyNewClass { get { return _MyNewClass ?? (_MyNewClass = new MyNewClass()); } set { _MyNewClass = value; } } private IMyNewClass _MyNewClass;
Another example is very similar but used IoC.
/// <summary>This is an example of a Lazy Injectable that will eventually be replaced with constructor injection in the future.</summary> internal IMyNewClass MyNewClass { get { return _MyNewClass ?? (_MyNewClass = IoCContainer.Resolve<IMyNewClass>()); } set { _MyNewClass = value; } } private IMyNewClass _MyNewClass;
Why use a Lazy Injectable?
Normally, you wouldn’t. Both examples above are antipatterns. In the first example, while it does use the interface, it also couples the code to an implementation. In the second example, it couples the code to an IoC container. This is essentially the well-known ServiceLocator antipattern.
However, it is very handy to use temporarily when working with legacy code to either write new code or refactor legacy code into with modern, quality coding practices. Even the top book on DI/IoC for CSharp mentions that less effective antipatterns can be usefully temporarily when refactoring legacy code.
Example Of When to Use
We all know that Dependency Injection (DI) and Inversion of Control (IoC) with constructor injection of interfaces, not concretes, is the preferred method of writing code. With DI/IoC and constructor injection, it is straight-forward to do object composition in the composition root. However, when working in legacy code without DI/IoC, that isn’t always possible to. Let’s say there is a class that is has over 100 references in code you can’t touch to the constructor. Changing the constructor is not an option. You need to change code to this legacy class. Remember, it doesn’t support DI/IoC with constructor injection and there is not composition root.
Let’s call this legacy class, MyLegacyClass.
However, you want to write you new code with DI/IoC with constructor injection.
public interface IMyNewClass { void SomeMethod(); } public class MyNewClass { private readonly ISomeDependency _someDependency; public MyNewClass(ISomeDependency someDependency) { _someDependency = someDependency; } public void SomeMethod() </pre><pre> { // .. some implementation } }
So without a composition root, how would we implement this in MyLegacyClass? The Lazy Injectable Property.
public class MyLegacyClass { public MyLegacyClass () { } /// <summary>This is an example of a Lazy Injectable that will eventually be replaced with constructor injection in the future.</summary> internal IMyNewClass MyNewClass { get { return _MyNewClass ?? (_MyNewClass = new MyNewClass()); } set { _MyNewClass = value; } } private IMyNewClass _MyNewClass; public void SomeLegacyMethod() { // .. some implementation MyNewClass.SomeMethod(); } }
What are the benefits? Why would I do this?
You do this because your current code is worse and you need it to be better, but you can’t get from bad to best in one step. Code often has to refactor from Bad to less bad, then from less bad to good, and then from good to best.
New code using new best-practice patterns. New code can be easily unit-tested.
Old code isn’t made worse. Old code can more easily be unit tested. The Lazy part of the property is effective because the interface can be mocked without the constructor of the dependency ever called.
This Lazy Injectable Property basically creates a micro-composition root that you can use temporatily as you add code or refactor. Remember, the long-term goal is DI/IoC with constructor injection everywhere. The process has to start somewhere, so start with new code and then require it for changed code. Eventually, more and more code will support constructor injection, until after a time of practicing this, the effort to migrate fully to DI/IoC with constructor injection everywhere becomes a small project.