Constructor Injection in C#/Unity?

35,102

Solution 1

One way to solve this would be to use an injection constructor with a named registration.

// Register timmy this way  
Person son = new Person("Timmy");  
container.RegisterInstance<Person>("son", son);  

// OR register timmy this way  
container.RegisterType<Person>("son", new InjectionConstructor("Timmy"));  

// Either way, register bus this way.  
container.RegisterType<Bus>(new InjectionConstructor(container.Resolve<Person>("son")));  

// Repeat for Joe / Train

Solution 2

Unless you register respectively "joe" and "timmy" as named dependencies, you can't be sure that "timmy" is injected into Schoolbus. In fact, if you attempt to register two instances of the same class as unnamed dependencies, you will have an ambiguous setup, and you will not be able to resolve Person at all.

In general, if you have to register a lot of named instances you are probably going about DI in the wrong way. The main idea of DI is to resolve Domain Services more than Domain Objects.

The primary idea of DI is to provide a mechanism that allows you to resolve abstract types (interfaces or abstract classes) into concrete types. Your example has no abstract types, so it doesn't really make a lot of sense.

Solution 3

Mark Seeman got it right. And I sympathize with your confusion. I went through it myself when I learned to use automatic dependency injection containers. The problem is that there are many valid and reasonable ways to design and use objects. Yet only some of those approaches work with automatic dependency injectorion containers.

My personal history: I learned OO principles of object construction and Inversion Of Control long before I learned how to use Inversion of Control containers like the Unity or Castle Windsor containers. I acquired the habit of writing code like this:

public class Foo
{
   IService _service;
   int _accountNumber;

   public Foo(IService service, int accountNumber)
   {
      _service = service;
      _accountNumber = accountNumber;
   }
   public void SaveAccount()
   {
       _service.Save(_accountNumber);

   }
}
public class Program
{
     public static void Main()
     {
        Foo foo = new Foo(new Service(),1234);
        foo.Save();
     }
}

In this design, my Foo class is responsible for saving accounts to the database. It needs an account number to do that and a service to do the dirty work. This is somewhat similar to the concreted classes you provided above, where each object takes some unique values in the constructor. This works fine when you instantiate the objects with your own code. You can pass in the appropriate values at the right time.

However, when I learned about automatic dependency injection containers, I found that I was no longer instantiating Foo by hand. The container would instantiate the constructor arguments for me. This was a great convenience for the services like IService. But it obviously does not work so well for integers and strings and the like. In those cases, it would provide a default value (like zero for an integer). Instead, I had been accustomed to passing in context-specific values like account number, name, etc... So I had to adjust my style of coding and design to be like this:

public class Foo
{
   IService _service;
   public Foo(IService service)
   {
      _service = service;
   }
   public void SaveAccount(int accountNumber)
   {
       _service.Save(accountNumber);

   }
}
public class Program
{
     public static void Main()
     {
        Foo foo = new Foo(new Service());
        foo.Save(1234);
     }
}

It appears that both Foo classes are valid designs. But the second is useable with automatic dependency injection, and the first is not.

Share:
35,102
JP Richardson
Author by

JP Richardson

https://www.exodus.com - all-in-one app to secure, manage and exchange blockchain assets like Bitcoin and Ethereum.

Updated on April 20, 2020

Comments

  • JP Richardson
    JP Richardson about 4 years

    I'm using C# with Microsoft's Unity framework. I'm not quite sure how to solve this problem. It probably has something to do with my lack of understanding DI with Unity.

    My problem can be summed up using the following example code:

    class Train(Person p) { ... }
    
    class Bus(Person p) { ... }
    
    class Person(string name) { ... }
    
    Person dad = new Person("joe");
    Person son = new Person("timmy");
    

    When I call the resolve method on Bus how can I be sure that the Person 'son' with the name 'timmy' is injected and when resolving Train how can I be sure that Person 'dad' with then name 'joe' is resolved?

    I'm thinking maybe use named instances? But I'm at a loss. Any help would be appreciated.

    As an aside, I would rather not create an IPerson interface.

  • JP Richardson
    JP Richardson over 14 years
    Thanks for your insightful response. Although you may be right, DI and IoC are new concepts to me so I'm still trying to figure out the best design.
  • Daniel Auger
    Daniel Auger over 14 years
    JP, do check out his book. I've found it to be very insightful.
  • JP Richardson
    JP Richardson over 14 years
    Awesome... it sounds intriguing. I just started reading the intro chapter.
  • McBainUK
    McBainUK over 13 years
    Shouldn't the 2nd Main() method be more like: Foo foo = new Foo(new Service()); foo.Save(1234); ?
  • The Light
    The Light over 12 years
    how would that be possible to store these in the config file rather than hardcoding?
  • sethidev
    sethidev about 9 years
    As a matter of fact it should be foo.SaveAccount(1234) :)
  • Thomas
    Thomas almost 8 years
    @MarkSeemann what about the injection of shared resources, i.e. a ConcurrentQueue<T> shared between two controllers? Is that also a valid candidate for dependency injection?
  • Mark Seemann
    Mark Seemann almost 8 years
    @Thomas How else would you share them?
  • Thomas
    Thomas almost 8 years
    Interesting not about the int initialization there. I just had a very similar case (entirely different domain) where I needed to initialize timeout -- abstracted that out to a method,
  • Thomas
    Thomas almost 8 years
    @MarkSeemann good point. Just thinking out loud about your point "allows you to resolve abstract types (interfaces or abstract classes) into concrete types. Your example has no abstract types". (Not trying to be nit picky, at all; just double checking)
  • Mark Seemann
    Mark Seemann almost 8 years
    @Thomas People mostly use Dependency Injection for the sake of polymorphism, but concrete dependencies are completely fine as well.