C# 构造函数注入和默认重载

C# 构造函数注入和默认重载,c#,.net,dependency-injection,C#,.net,Dependency Injection,假设我们有 public interface ITimestampProvider { DateTime GetTimestamp(); } 以及一个使用它的类 public class Timestamped { private ITimestampProvider _timestampProvider public Timestamped(ITimestampProvider timestampProvider) { // arg null

假设我们有

public interface ITimestampProvider
{
    DateTime GetTimestamp();
}
以及一个使用它的类

public class Timestamped
{
    private ITimestampProvider _timestampProvider

    public Timestamped(ITimestampProvider timestampProvider)
    {
        // arg null check

        _timestampProvider = timestampProvider;
    }

    public DateTime Timestamp { get; private set; }

    public void Stamp()
    {
        this.Timestamp = _timestampProvider.GetTimestamp();
    }
}
以及默认实现:

public sealed class SystemTimestampProvider : ITimestampProvider
{
    public DateTime GetTimestamp()
    {
        return DateTime.Now;
    }
}
引入这个构造函数是有帮助的还是有害的

public Timestamped() : this(new SystemTimestampProvider())
{}

这是一个一般性问题,即时间戳不是有趣的部分。

我不会提供该构造函数。当您的IoC可能被配置为使用OtherTimestampProvider()时,这样做会使调用new TimeStamped并使用new SystemTimestampProvider()获取实例变得非常容易

一天结束的时候,你会花很多时间去调试为什么你会得到错误的时间戳


如果你只提供第一个构造函数,你可以做一个简单的查找SystemTimestampProvider的用法,找出谁(错误地)使用了该提供程序而不是IoC配置的提供程序。

一般来说,我不这么认为。。。这取决于您使用依赖项注入的目的。当我使用DI进行单元测试时,当注入的实例为null时,我通过实例化从属对象的生产版本来做同样的事情(或多或少)。。。然后我有一个重载,它不接受一个参数,并委托给一个可以。。。我对生产代码使用无参数的方法,并为单元测试方法注入测试版本

如果您谈论的是IOC容器应用程序,otoh,那么您需要小心,不要干扰配置设置告诉容器以不清楚的方式执行的操作

   public class EventsLogic
   { 
       private readonly IEventDAL ievtDal;
       public IEventDAL IEventDAL { get { return ievtDal; } }

       public EventsLogic(): this(null) {}
       public EventsLogic(IIEEWSDAL wsDal, IEventDAL evtDal)
       {
          ievtDal = evtDal ?? new EventDAL();
       }
    }

我试图避免这种情况——在一些地方,我发现它是一个有用的设计,但更多的时候,我发现它只会导致我犯错误,这可能会让我有点困惑

通过使用依赖项注入容器(我使用StructureMap)来管理所有这些连接,大大减少了对默认注入对象的需求——DI容器确保您始终获得一个可以使用的具体实例

我仍然想使用您建议的构造函数的唯一地方是在我的单元测试中,但最近我从使用伪对象或模拟对象中获得了更大的价值


在某些情况下,使用默认的依赖对象是正确和有用的设计,但一般来说,我认为您只是引入了紧密耦合,不会增加很多价值。

这既没有帮助也没有有害。这带来了一个美学问题,因为只有当您的设计允许属性设置器注入时,您才将DI限制为构造函数注入

另一个选项是实现返回默认实现的getter:

public DateTime Timestamp
{
    get { return _timestampProvider??new SystemTimestampProvider(); }
    set { _timestampProvider = value; }
}

或者,如果担心在堆中创建太多的对象,可以使用单例实现上述功能。

我认为这取决于场景,基本上取决于代码的使用者(库与应用程序)以及是否使用IoC容器

  • 如果您使用的是IoC容器,而这不是公共API的一部分,那么就让容器来完成繁重的工作,只需要一个构造函数。添加no-args构造函数只会让事情变得混乱,因为您永远不会使用它

  • 如果这是公共API的一部分,则保留这两个API。如果您正在使用IoC,只需确保您的IoC找到“最贪婪”的构造函数(参数最多的构造函数)。不使用IoC,但使用API的人会喜欢不必为了使用对象而构建整个依赖关系图

  • 如果您没有使用IoC容器,但只想使用mock进行单元测试,请保留no args构造函数,并将贪婪构造函数设置为内部。为单元测试程序集添加InternalsVisibleTo,以便它可以使用贪婪构造函数。如果只是单元测试,那么就不需要额外的公共API表面


    • 我的团队使用这种方法取得了巨大成功。我建议做一个更改:
      将时间戳提供程序设置为只读。这迫使提供者在构造时具有确定性,并将消除bug

      public class Timestamped
      {
          private readonly ITimestampProvider _timestampProvider;
      
          public Timestamped(ITimestampProvider timestampProvider)
          {
              _timestampProvider = timestampProvider;
          }
      
          public Timestamped(): this(new SystemTimestampProvider())
          { }
      }
      

      也就是说,我们一直在关注新技术,包括DI框架。如果我们为了更好的东西放弃了这种技术,我会让你知道。

      你用的是什么样的依赖注入?Castle?就这个问题而言,没有。这是一个通用的API查询。我已经更新了这个问题,删除了“注入”的含义。虽然IoC不是这里场景的一部分,但我假设大多数实现都会在注册时注入提供程序,否则使用默认构造函数,这意味着如果不重写
      ITimestampProvider
      ,则只能获得
      SystemTimestampProvider
      ,这才是真正的意图。想法?这将是公共API的一部分,所以我选择保留重载。只是因为违约是合理的,我才考虑到这一点。我希望这个问题处于国际奥委会的真空状态,以避免纯粹出于技术考虑而损害设计。