Constructor Injection in C#/Unity?
Asked Answered
B

3

16

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.

Billion answered 6/1, 2010 at 18:41 Comment(0)
G
19

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
Genevieve answered 6/1, 2010 at 19:14 Comment(1)
how would that be possible to store these in the config file rather than hardcoding?Pangenesis
C
35

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.

Cornall answered 6/1, 2010 at 19:21 Comment(7)
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.Billion
JP, do check out his book. I've found it to be very insightful.Genevieve
Awesome... it sounds intriguing. I just started reading the intro chapter.Billion
@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?Jumper
@Jumper How else would you share them?Cornall
@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)Jumper
@Jumper People mostly use Dependency Injection for the sake of polymorphism, but concrete dependencies are completely fine as well.Cornall
G
19

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
Genevieve answered 6/1, 2010 at 19:14 Comment(1)
how would that be possible to store these in the config file rather than hardcoding?Pangenesis
A
15

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.SaveAccount(1234);
     }
}

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

Apocalypse answered 4/9, 2010 at 11:41 Comment(3)
Shouldn't the 2nd Main() method be more like: Foo foo = new Foo(new Service()); foo.Save(1234); ?Nativeborn
As a matter of fact it should be foo.SaveAccount(1234) :)Lewison
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,Jumper

© 2022 - 2024 — McMap. All rights reserved.