C#/Unity中的构造函数注入?

16

我正在使用Microsoft的Unity框架和C#。我不太确定如何解决这个问题,它可能与我对Unity中DI的理解不足有关。

我的问题可以用以下示例代码概括:

class Train(Person p) { ... }

class Bus(Person p) { ... }

class Person(string name) { ... }

Person dad = new Person("joe");
Person son = new Person("timmy");

当我在 Bus 上调用 resolve 方法时,我如何确保名称为 'timmy' 的 Person 'son' 被注入,而在解析 Train 时,如何确保名称为 'joe' 的 Person 'dad' 被解析?

我在思考是否使用命名实例?但是我很困惑。任何帮助将不胜感激。

另外,我宁愿不创建 IPerson 接口。

3个回答

35

除非你分别将“joe”和“timmy”注册为命名依赖项,否则你无法确定“timmy”是否注入了Schoolbus。实际上,如果您尝试将同一个类的两个实例注册为未命名依赖项,则会产生模糊设置,并且您将无法解析Person

一般来说,如果您必须注册大量命名实例,则可能正在错误地使用DI。 DI的主要思想是更多地解决领域服务而不是领域对象

DI的主要思想是提供一种机制,允许您将抽象类型(接口或抽象类)解析为具体类型。您的示例没有抽象类型,因此并没有太多意义。


感谢您的深入回复。虽然您可能是正确的,但依赖注入和控制反转对我来说是新概念,因此我仍在努力找出最佳设计方案。 - JP Richardson
2
JP,请一定要看看他的书。我发现它非常有深度。 - Daniel Auger
太棒了...听起来很有趣。我刚开始阅读介绍章节。 - JP Richardson
@MarkSeemann 那么共享资源的注入呢,例如在两个控制器之间共享一个 ConcurrentQueue<T> ,这也是依赖注入的有效候选吗? - Thomas
@MarkSeemann 很有道理。我只是大声思考你的观点“允许您将抽象类型(接口或抽象类)解析为具体类型。您的示例没有抽象类型”。(一点也不想挑剔,只是再次确认) - Thomas
显示剩余2条评论

19

解决这个问题的一种方法是使用一个带有命名注册的注入构造函数。

// 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

2
如何将这些内容存储在配置文件中而不是硬编码? - The Light

15

马克·西曼说得对。我理解你的困惑。当我学习使用自动依赖注入容器时,我自己也经历了这个过程。问题在于,有许多有效和合理的设计和使用对象的方式,但只有其中一些方法能够与自动依赖注入容器配合工作。

我的个人经历:在学习如何使用Unity或Castle Windsor等控制反转容器之前,我就已经学习了面向对象构建和控制反转的OO原则。我养成了编写以下代码的习惯:

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();
     }
}
在这个设计中,我的Foo类负责将账户保存到数据库中。它需要一个账户号码来完成此操作,并且需要一个服务来处理相关工作。这与您提供的具体类有些相似,每个对象在构造函数中都需要一些独特的值。当您使用自己的代码实例化对象时,这种方法很有效。您可以在适当的时间传递适当的值。
然而,当我了解到自动依赖注入容器时,我不再手动实例化Foo。容器会为我实例化构造函数的参数。这对于像IService这样的服务非常方便。但对于整数和字符串等情况,这显然并不太好。在这些情况下,它会提供默认值(例如整数的零)。相反,我习惯于传递上下文相关的值,如账户号码、名称等。因此,我不得不调整我的编码和设计风格以适应这种情况:
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);
     }
}

看起来两种Foo类的设计都是合法的。但第二种能够与自动依赖注入一起使用,而第一种则不能。


4
第二个Main()方法不应该更像这样吗:Foo foo = new Foo(new Service());foo.Save(1234); - McBainUK
事实上,应该是foo.SaveAccount(1234) :) - sethidev
关于int初始化的有趣之处在于,我曾经遇到过非常相似的情况(完全不同的领域),我需要初始化超时时间 - 将其抽象成一个方法。 - Thomas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接