简体   繁体   中英

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

I am just learning to use dependency injection with Ninject, and am using System.IO.Abstractions to abstract the filesystem. I am trying to use Ninject to bind DirectoryInfoBase to DirectoryInfo as so:

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

But am getting the error

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

What am I missing here? I thought the goal of libraries such as this was to be able to perform these sorts of tasks?

System.IO.Abstractions is using the Adapter Pattern . It is a trick used for certain types that don't have any abstraction (abstract class or interface) in order to use them with DI. Since there is no way to add an abstraction to an existing type in .NET, a wrapper (adapter) is created which has an abstraction (in this case, an abstract class) in order to use to loosely couple the implementation.

The problem here is that you are not using the wrapper, you are using the implementation directly.

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

However, there is another gotcha here - DirectoryInfo requires a directory path as a constructor argument. So this means it typically makes more sense to use an Abstract Factory so it can be created at runtime when the directory path is known. In this case, it makes more sense to inject the factory into your service and then call the method to create the instance at runtime. The author of System.IO.Abstractions made the factory internal, but you can build one just the same.

[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();
    }
}

And then configure the container to inject a factory that can create multiple runtime instances rather than a single instance.

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

But there is another trick the author of System.IO.Abstractions used to take it a step further. He made an Aggregate Service that can be injected and provide many of the services that types in the System.IO namespace provide in a loosely-coupled way.

So, rather than making your own factory, you could instead inject the existing IFileSystem service in order to gain access to virtually any of the services the System.IO namespace provides.

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

You would then configure the container just to inject IFileSystem to gain all of the functionality of System.IO.

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

You can't use that particular binding because DirectoryInfo doesn't inherit from or implement DirectoryInfoBase .

You might have been misled by the fact you can do cast a DirectoryInfo to a DirectoryInfoBase using

DirectoryInfo dirInfo;
DirectoryInfoBase dirInfoBase = (DirectoryInfoBase)dirInfo;

but that is only possible because of an implicit cast operator in DirectoryInfoBase :

public static implicit operator DirectoryInfoBase(DirectoryInfo directoryInfo)
{
    if (directoryInfo == null)
        return null;
    return new DirectoryInfoWrapper(directoryInfo);
}

I'm not familiar with System.IO.Abstractions but why don't you just inject the IFileSystem , like in the example with

ninject.Bind<IFileSystem>().To<FileSystem>();

If you have a IFileSystem you can do

fileSystem.DirectoryInfo.FromDirectoryName(directoryName)

to get a DirectoryInfoBase object.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM