在应用依赖注入时,Func <in T,out TResult>是否适合用作ctor arg?

Roo*_*ian 13 c# delegates dependency-injection inversion-of-control func

例:

public class BusinessTransactionFactory<T>  where T : IBusinessTransaction
{
    readonly Func<Type, IBusinessTransaction> _createTransaction;

    public BusinessTransactionFactory(Func<Type, IBusinessTransaction> createTransaction)
    {
        _createTransaction = createTransaction;
    }

    public T Create()
    {
        return (T)_createTransaction(typeof(T));
    }
}
Run Code Online (Sandbox Code Playgroud)

使用相同的容器设置代码:

public class DependencyRegistration : Registry
{
    public DependencyRegistration()
    {
        Scan(x =>
        {
            x.AssembliesFromApplicationBaseDirectory();
            x.WithDefaultConventions();
            x.AddAllTypesOf(typeof(Repository<>));
            x.ConnectImplementationsToTypesClosing(typeof(IRepository<>));
        });

        Scan(x =>
        {
            x.AssembliesFromApplicationBaseDirectory();
            x.AddAllTypesOf<IBusinessTransaction>();
            For(typeof(BusinessTransactionFactory<>)).Use(typeof(BusinessTransactionFactory<>));
            For<Func<Type, IBusinessTransaction>>().Use(type => (IBusinessTransaction)ObjectFactory.GetInstance(type));
        });


        For<ObjectContext>().Use(() => new ManagementEntities());
    }
}
Run Code Online (Sandbox Code Playgroud)

你怎么看?

Mar*_*ann 25

机械学

在机械层面上,使用委托是完全没问题的,因为委托基本上是一个匿名的角色接口.换句话说,无论是注入委托还是接口或抽象基类都无关紧要.

概念

在概念层面,重要的是要记住依赖注入目的.您可能因为与我不同的原因使用DI,但IMO的目的是提高代码库的可维护性.

是否通过注入代理而不是接口来实现这一目标是值得怀疑的.

委托作为依赖项

第一个问题是代表沟通意图的程度.有时,接口名称本身会传达意图,而标准委托类型几乎不会.

例如,单独的类型在这里没有太多意图:

public BusinessTransactionFactory(Func<Type, IBusinessTransaction> createTranscation)
Run Code Online (Sandbox Code Playgroud)

幸运的是,该createTranscation名称仍然暗示了委托所扮演的角色,但只是考虑(为了论据的缘故)如果作者不那么小心,那么构造函数的可读性如何:

public BusinessTransactionFactory(Func<Type, IBusinessTransaction> func)
Run Code Online (Sandbox Code Playgroud)

换句话说,使用委托将焦点从类型名称转移到参数名称.这不一定是个问题 - 我只是指出你需要意识到这种转变.

可发现性与可组合性

另一个问题是关于实现依赖关系的类型的可发现性与可组合性.举个例子,这两个实现公共Func<Type, IBusinessTransaction>:

t => new MyBusinessTransaction()
Run Code Online (Sandbox Code Playgroud)

public class MyBusinessTransactionFactory
{
    public IBusinessTransaction Create(Type t)
    {
        return new MyBusinessTransaction();
    }
}
Run Code Online (Sandbox Code Playgroud)

但是,在类的情况下,具体的非虚Create方法与所需的委托匹配几乎是偶然的.它非常可组合,但不是很容易被发现.

另一方面,当我们使用接口时,类在实现接口时成为is-a关系的一部分,因此通常更容易找到所有实现者和组并相应地组合它们.

请注意,这不仅适用于程序员阅读代码,也适用于DI容器.因此,在使用接口时,更容易实现Convention over Configuration.

与RAP相比,1:1接口

有些人注意到,当尝试使用DI时,他们最终会得到很多1:1接口(例如IFoo,相应的Foo类).在这些情况下,接口(IFoo)似乎是多余的,似乎很容易摆脱接口并使用委托.

但是,许多1:1接口实际上是违反重用抽象原则的症状.当您重构代码库以在多个位置重用相同的抽象时,将抽象的角色显式建模为接口(或抽象基类)是有意义的.

结论

接口不仅仅是机制.它们在应用程序代码库中明确地模拟角色.中心角色应该由接口表示,而一次性工厂及其同类可以作为代理使用和实现.

  • 就可读性而言,您可以简单地定义自己的委托,而不是使用内置的Func和Action.这也将解决可发现性和重复签名问题. (4认同)
  • 我喜欢使用委托作为依赖项.首先,它使单元测试更加简单.一个有趣的副作用是您可以根据委托定义API并避免实际编写任何代码 - 例如,仅查看OWIN规范.一旦开始注入委托,代码开始看起来更有用.我写了一段时间回顾一个'currying容器'如何允许一个人完全使用代表编写C#代码(http://mikehadlow.blogspot.com/2010/03/thought-experiment-curry-facility.html). (3认同)
  • 定义自定义委托如何帮助发现?即使使用自定义委托类型,我也看不到任何*easy*方法来扫描程序集以获得匹配方法. (2认同)

Ste*_*ven 5

我个人不喜欢Func<T>在我的类中看到参数作为依赖项,因为我认为它使代码的可读性降低,但我知道许多开发人员不同意我的观点.某些容器(如Autofac)甚至允许您解析Func<T>委托(通过返回a () => container.Resolve<T>()).

但是,有两种情况我不介意注入Func<T>代表:

  1. 当获取Func<T>构造函数参数的类位于组合根中时,因为在这种情况下它是容器配置的一部分,我不考虑应用程序设计的那一部分.
  2. Func<T>在应用程序中以单一类型注入时.当更多服务依赖于相同的服务时Func<T>,创建一个特殊的I[SomeType]Factory接口并注入它会更好.