Photo by Jordan Madrid on Unsplash
Using Dependency Injection and Service Locators to get References in C#
I received a coursework project in the second year of my studies. As it was the first time that the students on the course were expected to build a Java desktop app with a user interface (UI), the course lecturer gave us an introduction to creating UIs with the Abstract Window Toolkit (AWT) library.
I started writing other Java programs in my spare time after my studies, and eventually made the leap to C#. I knew of only two options for building UIs at the time: Windows Forms (WinForms), and the newer Windows Presentation Foundation (WPF). With the promise of newer technologies, I chose to learn WPF.
I was unfamiliar with markup languages at the time. I hadn’t written any HTML at that point, so I was puzzled by XAML (the markup language for WPF UIs). I would use Visual Studio’s built-in designer to create basic UIs. I’d then make small changes and examine the updated code. I slowly built up an understanding of how things worked. It was confusing but great fun. I felt a great sense of achievement with each piece that I learned. Every small win powered me on, helping me to build increasingly sophisticated UIs.
Service Locators
I eventually came across the concept of data binding and the DataContext
. Data binding in WPF works in a similar way to how it does in many modern front-end libraries and frameworks: you use a special markup syntax to indicate that parts of the UI should display data from a model and be kept in sync if either change. I learned that it was possible to do something other than data binding too: it was possible to declare objects in XAML as static resources (using the StaticResource
keyword) and read values from those.
Using a static resource was one way to declare the view model for a view, entirely within its XAML. However, each declaration created a new instance of the view model. I needed a way to reference the same view model instance from multiple places. This was possible with a service locator. In this pattern, a locator maintains a reference to a single view model. Any instance of the service locator would provide a reference to the same view model.
Dependency Injection
I came across Dependency Injection (DI) for the first time in what’s now one of my now former job roles. All the project’s service and repository classes had been configured as Singletons. This meant that wherever any of the dependencies were requested, the same object instance was provided for each respective type. Being new to it all, I didn’t realise that objects could be registered with different lifetime scopes. Given the similarities between dependency injection and service locators, I ended up with a few questions:
What’s the difference between using dependency injection and a service locator?
If they’re both ways of finding a single long-lived object instance, wouldn’t a service locator be simpler to implement and use?
Could we use the two approaches interchangeably?
A Comparison
Through further experience, I’m now in a better place to answer my own questions.
When dependencies are configured as Singletons, they do share one similarity: you have a container from which you can get references to specific objects. However, that’s really where the similarities end. When using a service locator, you need code inside the consuming class to instantiate the locator before asking for any dependencies.
With dependency Injection, an object’s dependencies would be supplied to it – often (but not necessarily) in the form of constructor parameters. The difference is subtle but significant. It means that less code is required (you wouldn’t need to instantiate the locator) and, more importantly, classes become more testable through looser coupling. When dependencies are specified as interfaces (and to an extent classes with virtual methods), you can easily substitute them for mocks and fakes in unit tests. This wouldn’t be possible when using a service locator: any dependencies provided by it could only be for implementations suitable at runtime.
As such, we can safely say that dependency injection and service locators are not interchangeable. Service locators are sometimes seen as an anti-pattern because of how their usage can lead to tighter code coupling. However, they still have their place. It’s possible to integrate dependency injection into many project types – these can range from Web facing APIs, to services, to desktop apps. However, it’s very difficult to declare a shared view model from within the markup of a view of an app that uses WPF (or a similar framework, e.g. Avalonia), without using a service locator as a static resource reference. That said, the framework being used may provide alternative ways to bind view models to views. It’s also simple to set them from a view’s code-behind.
Summary
Broadly speaking, dependency injection and service locators can both be used to get references to an object’s dependencies. However, the two approaches are completely different.
Service locators require code to be written within the receiving classes to use them. This approach encourages tighter coupling, making classes more difficult to test.
Dependency injection involves setting up an injector at the application level (rather than class level) to automatically provide dependencies for a class. The code becomes more loosely coupled when dependencies are specified using interfaces and classes with overridable methods. This also has the (positive) side effect of increasing the testability of the code.
Using service locators can be considered an anti-pattern, and dependency injection is preferred in most cases. However, there may be cases where dependency injection isn’t possible. As always, use the best tool for the situation, for the project that you are working on.