为什么我不能将 System.IO.Abstractions 与 Ninject 绑定?

Why can't I bind System.IO.Abstractions with Ninject?

我正在学习使用 Ninject 的依赖注入,并且正在使用 System.IO.Abstractions 来抽象文件系统。我正在尝试使用 Ninject 将 DirectoryInfoBase 绑定到 DirectoryInfo

IKernel ninject = new StandardKernel();
ninject.Bind<DirectoryInfoBase>().To<DirectoryInfo>();

但是收到错误

Error 1 The type 'System.IO.DirectoryInfo' cannot be used as type parameter 'TImplementation' in the generic type or method 'Ninject.Syntax.IBindingToSyntax.To()'. There is no implicit reference conversion from 'System.IO.DirectoryInfo' to 'System.IO.Abstractions.DirectoryInfoBase'. C:\Users\Trevor\Dropbox\Code\PhotoOrganiser\PhotoOrganiser\Program.cs 13 13 PhotoOrganiserApp

我在这里错过了什么?我认为像这样的库的目标是能够执行这些类型的任务?

您不能使用该特定绑定,因为 DirectoryInfo 没有继承或实现 DirectoryInfoBase

您可能被以下事实误导了:您可以使用

DirectoryInfo 转换为 DirectoryInfoBase
DirectoryInfo dirInfo;
DirectoryInfoBase dirInfoBase = (DirectoryInfoBase)dirInfo;

但这是可能的,因为 DirectoryInfoBase:

中的隐式转换运算符
public static implicit operator DirectoryInfoBase(DirectoryInfo directoryInfo)
{
    if (directoryInfo == null)
        return null;
    return new DirectoryInfoWrapper(directoryInfo);
}

我不熟悉 System.IO.Abstractions 但你为什么不直接注入 IFileSystem,就像

的例子一样
ninject.Bind<IFileSystem>().To<FileSystem>();

如果你有一个IFileSystem你可以

fileSystem.DirectoryInfo.FromDirectoryName(directoryName)

得到一个DirectoryInfoBase对象。

System.IO.Abstractions 正在使用 Adapter Pattern。这是一种技巧,用于某些没有任何抽象(抽象 class 或接口)的类型,以便将它们与 DI 一起使用。由于无法向 .NET 中的现有类型添加抽象,因此创建了一个具有抽象(在本例中为抽象 class)的包装器(适配器),以便用于松耦合实现.

这里的问题是你没有使用包装器,你直接使用了实现。

IKernel ninject = new StandardKernel();
ninject.Bind<DirectoryInfoBase>().To<DirectoryInfoWrapper>()
    .WithConstructorArgument("instance", new DirectoryInfo(@"C:\Somewhere\"));

但是,这里还有另一个问题 - DirectoryInfo 需要目录路径作为构造函数参数。所以这意味着通常使用 Abstract Factory 更有意义,这样它就可以在目录路径已知的情况下在运行时创建。在这种情况下,将工厂注入您的服务然后调用该方法在运行时创建实例更有意义。 System.IO.Abstractions 的作者将工厂设置为内部工厂,但您可以照样建造一个。

[Serializable]
public class DirectoryInfoFactory : IDirectoryInfoFactory
{
    public DirectoryInfoBase FromDirectoryName(string directoryName)
    {
        var realDirectoryInfo = new DirectoryInfo(directoryName);
        return new DirectoryInfoWrapper(realDirectoryInfo);
    }
}

public class SomeService : ISomeService
{
    private readonly IDirectoryInfoFactory directoryInfoFactory;

    public SomeService(IDirectoryInfoFactory directoryInfoFactory)
    {
        if (directoryInfoFactory == null) 
            throw new ArgumentNullException("directoryInfoFactory");
        this.directoryInfoFactory = directoryInfoFactory;
    }

    public void DoSomething()
    {
         // The directory can be determined at runtime.
         // It could, for example, be provided by another service.
         string directory = @"C:\SomeWhere\";

         // Create an instance of the DirectoryInfoWrapper concrete type.
         DirectoryInfoBase directoryInfo = this.directoryInfoFactory.FromDirectoryName(directory);

         // Do something with the directory (it has the exact same interface as
         // System.IO.DirectoryInfo).
         var files = directoryInfo.GetFiles();
    }
}

然后配置容器以注入可以创建多个运行时实例而不是单个实例的工厂。

IKernel ninject = new StandardKernel();
ninject.Bind<IDirectoryInfoFactory>().To<DirectoryInfoFactory>();

但是 System.IO.Abstractions 的作者还使用了另一个技巧使它更进一步。他制作了一个 Aggregate Service 可以注入并提供许多在 System.IO 命名空间中类型以松散耦合的方式提供的服务。

因此,与其创建自己的工厂,不如注入现有的 IFileSystem 服务,以获得对 System.IO 命名空间提供的几乎任何服务的访问权限。

public class SomeService : ISomeService
{
    private readonly IFileSystem fileSystem;

    public SomeService(IFileSystem fileSystem)
    {
        if (fileSystem == null) 
            throw new ArgumentNullException("fileSystem");
        this.fileSystem = fileSystem;
    }

    public void DoSomething()
    {
         // The directory can be determined at runtime.
         // It could, for example, be provided by another service.
         string directory = @"C:\SomeWhere\";

         // Create an instance of the DirectoryInfoWrapper concrete type.
         DirectoryInfoBase directoryInfo = this.fileSystem.DirectoryInfo.FromDirectoryName(directory);

         // Do something with the directory (it has the exact same interface as
         // System.IO.DirectoryInfo).
         var files = directoryInfo.GetFiles();
    }
}

然后您将配置容器以注入 IFileSystem 以获得 System.IO 的所有功能。

IKernel ninject = new StandardKernel();
ninject.Bind<IFileSystem>().To<FileSystem>();