1

With .net core you can register "Services" which as I understand, simply means you can register types to concrete classes.

As such, I decided it's about time I learnt DI and practised it. I understand the concept, and with testing it is massively beneficial. However what confuses me is the idea of registering services and whether it's actually needed.

For example, if I have:

public class MyClass
{
    public MyClass(IDataContext)
    {
         ... store it 
    }
}

Then this means I can inject any class that implements the IDataContext, allowing for fakes and moqs in testing. But why would I register a service and map IDataContext to a concrete class in the startup? Is there something wrong with just using the following in other methods:

DataContext dc = new DataContext(); // concrete
var c = new MyClass(dc);

Edit

This question was around the point of using the container (services) rather than why use an interface in the constructor.

Fred Johnson
  • 2,539
  • 3
  • 26
  • 52

4 Answers4

2

Now those classes where you put this code

public class MyService
{
    public void DoSomething()
    {
        DataContext dc = new DataContext(); // concrete
        var c = new MyClass(dc);
        c.DoSomething();
    }
}

have a hard dependency on DataContext and MyClass. So you can't test MyService in isolation. Classes shouldn't care how other classes do what they do, they should only care that they do what they say they're going to do. That's why we use interfaces. This is separation of concerns. Once you've achieved this, you can unit test any piece of code in isolation without depending on the behavior of outside code.

Registering your dependencies up front in one location is also cleaner and means you can swap dependencies out by changing one location instead of hunting down all the usages and changing them individually.

In my code example at the top, MyService requires the usage of both DataContext and MyClass. Instead, it should be like this:

public class MyService
{
    private readonly IMyClass _myClass;

    public MyService(IMyClass myClass)
    {
        _myClass = myClass;
    }

    public void DoSomething()
    {
        _myClass.DoSomething();
    }
}

public interface IMyClass
{
    void DoSomething();
}

public class MyClass : IMyClass
{
    private readonly IDataContext _context;

    public MyClass(IDataContext context)
    {
        _context = context;
    }
    public void DoSomething()
    {
        _context.SaveSomeData();
    }
}

Now MyService isn't dependent on DataContext at all, it doesn't need to worry about it because that's not its job. But it does need something that fulfills IMyClass, but it doesn't care how it's implemented. MyService.DoSomething() can now be unit tested without depending on the behavior of other code.

If you weren't using a container to handle satisfying the dependencies, then you're probably introducing hard dependencies into your classes, which defeats the entire point of coding against an interface in the first place.

Testing in isolation is important. It's not a unit test if you're testing more than one finite piece of code. It's an integration test (which have their own value for different reasons). Unit tests make it quick and easy to verify a finite block of code works as expected. When a unit test isn't passing, you know right where the problem is and don't have to search hard to find it. So if a unit test depends on other types, or even other systems (likely in this case, DataContext is specific to a particular database) then we can't test MyService without touching a database. And that means the database must be in a particular state for testing, which means the test likely isn't idempotent (you can't run it over and over and expect the same results.)

For more information, I suggest you watch Deep Dive into Dependency Injection and Writing Decoupled Quality Code and Testable Software by Miguel Castro. The best point he makes is that if you have to use new to create an instance of an object, you've tightly coupled things. Avoid using new, and Dependency Injection is a pattern that enables you to avoid it. (using new isn't always bad, I'm comfortable with using new for POCO models).

mason
  • 31,774
  • 10
  • 77
  • 121
2

You can inject your dependencies manually. However this can get a very tedious task. If your services get bigger, you will get more dependencies, where each dependency on its own can have multiple dependencies.

If you change your dependencies, you need to adjust all usages. One of the main advantages of a DI container is, that the container will do all dependency resolving. No manual work required. Just register the service and use it wherever you want and how often you want.

For small projects this seems like too much overhead, but if your project grows a little, you will really appreciate this.

For fixed dependencies, which are strongly related and not likely to change, injecting them manually is fine.

Using a DI container has another advantage. The DI container will control the life cycle of its services. A service could be a singleton, transient (each request will get a new instance) or have scoped life time.

For example, if you have a transactional workflow. The scope could match the transaction. While in the transaction, requests to a service will return the same instance.

The next transaction will open a new scope and therefore will get new instances. This allows you to either discard or commit all instances of one transaction, but prevents that following transaction uses resources from the previous one.

Iqon
  • 1,920
  • 12
  • 20
0

You right, you can create all instances manually. In small projects it's an usual practice. The place in your project where links the classes is called Composition Root, and what you do is Constructor Injection.

IoC-libraries can simplify this code, especially considering complex cases like life-time scopes and group registration.

Mark Shevchenko
  • 7,937
  • 1
  • 25
  • 29
0

Inversion of control (of constructing an object)

The idea of this pattern is when you want to construct an object you only need to know about the type of the object and nothing about its dependencies or parameters.


Dependency injection

This pattern is taking the inversion of control pattern a step further by enabling you to directly inject an object into a constructor for example.

Again you only need to know the type of the object you want to get and the dependency container will inject an object.

You also don't need to know if a new object is constucted or if you get a allready existing reference.

The most common used type of dependency injection is constructor injection but you could inject your type into other places like methods.


Seperation of concerns

Generally you register a type by an interface to get rid of the dependency on the type.

This is very helpfull for mocking types when testing and it helps to use the open closed principle.


Martin Fowler on "Inversion of Control Containers and the Dependency Injection pattern".

NtFreX
  • 10,379
  • 2
  • 43
  • 63