C
C#14mo ago
__dil__

❔ Dependency Injection

Disclaimer: I come from a low-level language, never did Java and very new in C#. I started to read the Avalonia UI MVVM tutorial, and it mentioned that usually you'd use DI to avoid coupling the model and the viewmodel. This got me curious about what exactly is DI. My understanding is that you have a service that uses a dependency. Instead of using the dependency directly, you make the service use an interface for that service, and provide an instance of that interface somehow. So, the "replace concrete type with interface" I get. My main confusion is about how to provide the instance. From what I read, it's either 1) passing a concrete type instance to a constructor that expects the interface type. Or 2) Something involving "frameworks and "automatic registration". Option 1) is pretty intuitive, not much to be confused about here, but option 2) is complete gibberish to me.
25 Replies
Angius
Angius14mo ago
You provide the implementation to the DI container For example,
builder.Services.AddScoped<IProductService, ProductService>();
builder.Services.AddScoped<IProductService, ProductService>();
That tells the DI framework, that wherever an IProductService is requested, it should inject an instance of ProductService
Pobiega
Pobiega14mo ago
and there are 3 different lifetimes by default: Transient (new every time) Singleton (same every time) Scoped (same within the scope, whatever a scope is)
__dil__
__dil__OP14mo ago
With all due respect, you're like 10 steps ahead. The implementation of what? What's a DI container? What's a DI framework? What does "requested" and "injected" mean in this context
Pobiega
Pobiega14mo ago
a scope is defined by your app. for ASP it defaults to request scope
__dil__
__dil__OP14mo ago
I have no experience with DI so this is all very obscure to me.
Angius
Angius14mo ago
The implementation of the interface The place that stores all instances that are supposed to be injected A framework that makes DI possible Requested/injected can vary, but in case of ASP.NET Core, for example, it's the interface you place in the constructor
Jimmacle
Jimmacle14mo ago
tl;dr DI is a magic box where you dump a bunch of objects in, and when you tell it to create new objects it figures out which of those objects to pass into the new objects' constructors this is less the concept of DI itself and more how DI implementations tend to work in C#
JakenVeina
JakenVeina14mo ago
yeah, the concept that people often refer to as DI is actually IoC: Inversion of Control Dependency Injection is what makes IoC possible IoC is just saying "I'm going to have one master container that's responsible for maintaining object lifetimes". I.E. reusing certain types of objects across multiple differerent consumers, and then cleaning up/disposing those objects when they're no longer needed
__dil__
__dil__OP14mo ago
Ok I see, that kind of explains the large variations in answers I found on the subject. It's unclear to me why I would want that in the first place. What's wrong with simply passing the object instead of relying on a black box to do it? In the context of the original question, why would you want that for an MVVM pattern?
Pobiega
Pobiega14mo ago
because in large applications, you might have hundreds or thousands of things. passing them around yourself gets very messy, very fast - not to mention, how do you keep track of their lifetimes etc in that scenario letting the DI container handle all that for you based on the rules you told it (transient, scoped, singleton) makes life easier also, DI containers can be unittested (ie, verify that every registered thing can be resolved and that they dont have conflicting lifetimes)
__dil__
__dil__OP14mo ago
I don't really see what's inherently "messy" about it and how the DI container improves that
Jimmacle
Jimmacle14mo ago
at some point you're going to need some construct that manages instances of objects to pass into other objects you could do it by hand and for a simple application that's probably fine but DI containers like the standard Microsoft.DependencyInjection one being mentioned here do it for you
__dil__
__dil__OP14mo ago
To be clear, I'm not debating whether it's useful/needed or not, I simply don't have a reference to understand what things would look like with and without using DI.
Jimmacle
Jimmacle14mo ago
i don't think i have a concise example handy, but think about a scenario where you have to instantiate a class that needs an instance of another class, and that one needs other different classes, and so on eventually having to write all the boilerplate to set up all those dependencies becomes unmanageable
__dil__
__dil__OP14mo ago
For the record, my goto in a situation like that is to make builder APIs and config datastructures, or something along those lines but ok yeah I can see how that's a difficult situation that needs a clever solution
Jimmacle
Jimmacle14mo ago
i suppose you could consider a dependency injection system as a kind of generic builder you configure it ahead of time and tell it what services to include as what implementations etc, then it does all the work of resolving the dependency graphs when you want to instantiate something then say you want to swap out an implementation for something else, now you change a single line in your DI configuration instead of having to go through a bunch of code that specifically references that type
__dil__
__dil__OP14mo ago
Ok I see where this is going. I still have to get some actual practice with it, but I understand much better now. Thanks for taking the time to explain @Jimmacle As a closing question, let's say I'm working on a Avalonia MVVM project and want to setup DI, what would be the go to DI framework?
Jimmacle
Jimmacle14mo ago
i personally try to use Microsoft.DependencyInjection everywhere for consistency, iirc avalonia uses splat internally for some things (maybe just with reactiveui)
__dil__
__dil__OP14mo ago
I use ReactiveUI so might as well use that too 🤷 thanks!
Jimmacle
Jimmacle14mo ago
you'll want to double check it, that may be one that's more like a service locator than DI as in it's more like a bag of objects than a framework that can construct instances for you i haven't used it myself but i think that's why i avoided going with it for avalonia applications
__dil__
__dil__OP14mo ago
oh ok interesting 🤔
Angius
Angius14mo ago
One good example I find comes from my own codebase. I had to switch to a different transactional email provider in my ASP.NET Core app. Instead of having to change every new FooMainer() to new BarMailer() everywhere that used it, I just changed
builder.Services.AddSingleton<IMailer, FooMailer>();
builder.Services.AddSingleton<IMailer, FooMailer>();
into
builder.Services.AddSingleton<IMailer, BarMailer>();
builder.Services.AddSingleton<IMailer, BarMailer>();
Jimmacle
Jimmacle14mo ago
i use it to dynamically pick implementations at startup, for example swapping out a real email sending service to a dummy one that just logs the emails for testing
JakenVeina
JakenVeina14mo ago
I'm sure the others have done it justice already, but "because management of object lifetimes is just as much of an issue in MVVM as it is in any other OOP architecture" the go-to example is almost always database access picture a button that you click to trigger a form or datagrid to load from a database query your command execute method (or click event handler or whatever) would look something like this
private async Task OnLoadCommandExecuted()
{
using var loadScope = _serviceScopeFactory.CreateScope();

var loadedData = await serviceScope.ServiceProvider.GetRequiredService<DataLoadingService>().LoadDataAsync();

DisplayedRecords.AddRange(loadedData.Records);
}
private async Task OnLoadCommandExecuted()
{
using var loadScope = _serviceScopeFactory.CreateScope();

var loadedData = await serviceScope.ServiceProvider.GetRequiredService<DataLoadingService>().LoadDataAsync();

DisplayedRecords.AddRange(loadedData.Records);
}
what this accomplishes is that you create a new short-lived database connection to perform your database I/O, and dispose of it when you're done because your database services, whatever they might look like, are registered with the IoC container as "scoped", so they are created new for each scope, and shared among everything within the scope that wants to do database I/O maybe that's an Entity Framework DbContext, maybe it's an ADO SqlConnection, whatever for simple scenarios, sure, you could just create a DbContext or SqlConnection directly, in lieu of _serviceScopeFactory.CreateScope() but it shouldn't be hard to imagine more complex scenarios maybe DataLoadingService actually calls out to some other services to do multiple database calls, and then assemblebthe results together maybe it checks a cache first, which would be another service registered with the IoC container, this time as singleton maybe the operation involves both database I/O and I/O with some HTTP API.
Accord
Accord14mo ago
Was this issue resolved? If so, run /close - otherwise I will mark this as stale and this post will be archived until there is new activity.
Want results from more Discord servers?
Add your server