The Dependency Inversion Principle with .NET Framework classes
Asked Answered
G

3

8

I'm trying to understand SOLID principles, in particular The Dependency Inversion Principle.

In this is SO answer it is explained very well.

I think I have understood that I can't create any instance of a class inside my class. Is it right?

But if I have to save to disk some content, can I create an instance of System.IO.File or do I have to inject it?

I don't understand where is the limit, if I can't instance my own classes or if I can't either instance .NET Framework classes (or whatever other framework).

UPDATE:
I think File is a bad example because is declared as static.

By the way, does this principle apply to static classes?

Gustie answered 20/7, 2017 at 11:13 Comment(8)
You would probably want use the instance of System.IO.File inside a method right? You don't inject methods, but classes so I don't see a problem here. What I mean is, wrap it inside a service and inject that.Brenza
Actually you may want to inject your own file management interface (of which one concrete implementation does create a File object but nothing more), otherwise your code will become impossible to test.Freemasonry
You wouldn't have to inject that but you could inject your own FileService or something similar to handle the saving to diskLacombe
I think File is a bad example because it is declared as static: referencesource.microsoft.com/#mscorlib/system/io/…Gustie
As @Freemasonry said, inject your own IFileSaver and use that to save the file. It helps with testing and mocking of your serviceLacombe
A common example of this is the DateTime.Now method, you should see plenty of code snippets that show you how you would abstract that into an interface. (e.g. an IDateTimeNowProvider)Freemasonry
@Freemasonry Ok, I think I have to reformulate my question: How can I use System.IO.File without violating the dependency inversion principle? Or maybe I haven't understood the injection of my own file manager interface.Gustie
@Freemasonry awesome comment, I was exactly giving this example in my answer. It may look overthinking, but I actually think this is a good idea.Foliole
F
3

The S of SOLID stands for SRP (Single Responsibility Principle). You won't violate it by using System.IO.File inside a class directly, once you keep that class with one single responsibility.

It's a good idea trying to abstract the purpose behind using System.IO.File. Let's suppose you need it to generate a log. Then you would probably do something like:

public interface IMyLogger
{
    void GenerateLog(IEnumerable<string> content);
}

public class FileLogger: IMyLogger
{
    public void GenerateLog(IEnumerable<string> content)
    {
        System.IO.File.WriteAllLines("C:/Log", content);
    }
}

Maybe it's not just a log, it's something more important, like generating a file so other system/app (even external) read it and do some job.

If you are trying to use a DDD approach, the interface could belong to your domain, and the implementation could belong in the application. Then you register your interface as a service and inject it.

The class which needs an IMyLogger actually doesn't need to know how is the log being generated, it just needs the job to be done.

You can apply the same idea when you need to send an email inside some business logic in your domain. Instead of making a connection to an Exchange inside your domain directly, create an interface INotifier and a MailNotifier implementing it to be injected.

Foliole answered 20/7, 2017 at 11:24 Comment(5)
Thanks for your answer. But you are using System.IO.File in your FileLogger class without injecting it. Where does injection stop? Does your FileLogger class violating that principle? Maybe I'm confuse or I haven't understood anything.Gustie
You're welcome. No, once the only purpose of my FileLogger is to use System.IO.File to generate logs. SOLID tells us about the Single Responsibility Principle, which in short means we shouldn't use a class/service to do all the sort of things, it should have one responsibility. If you have a class that do some calculus and at the same time uses System.IO.File after that, that class already has more than one responsibility, thus violating SRP (the S of SOLID). That's why FileLogger is not violating it.Foliole
I'm not sure if I have understood your answer but I'm asking about the D principle, not the S. It is strange for me that I can create an instance of a class in another class without violating the dependency inversion principle. Thanks.Gustie
@Gustie I like to think each of SOLID statements work together. Like Kapol explained, you will eventually need to use System.IO.File somewhere. My idea here is quite simple, I'm just creating a wrapper for it, and extracting an interface from it to be injected. Therefore, you apply D with respect to S. You would violate D if you had a class which did not have the responsibility to write files to disk, but used System.IO.File inside it, instead of asking for a service (like IMyLogger) to be injected. Actually you would be violating D and S, that's why I think they work together.Foliole
You would inject the logger. IMyLogger is a contract for a logging service. FileLogger is an implementation of this service. The word "service" is used in a very general sense and just designates a piece of code that does something for you and has nothing to do with a web service or the like. Generally, you are injecting services and components.String
R
2

Somewhere down the chain of dependencies you will need to use the concrete class directly. Even if you use a DI framework like Ninject, the framework itself will create an instance of the concrete type, so it will not be injected into the framework (which wouldn't make sense, of course).

You can only abstract something away to a certain level. It will vary from project to project - you have to ask yourself if you need another level of abstraction (be it for modularity, unit testing etc.). I think this is very important - you want to be pragmatic, not create layers upon layers of abstractions just for the sake of it.

By the way, does this principle apply to static classes?

Yes, it does. But with static classes you have to introduce a wrapper, which will delegate calls to the static class, because a static class cannot implement interfaces.

Roberson answered 20/7, 2017 at 11:31 Comment(1)
Static behavior can be simulated by a singleton object that can be injected. In most DI frameworks allow you to specify the lifetime of an object as singleton. I.e. they would always return the same instance of a service you ask for.String
S
1

There is no point in applying a principle just for the sake of it. Think in a pragmatic way.

If you want to unit-test a method that uses hard coded file accesses, your unit tests will access these files. This is usually not desired as you must set up or clean up these files. To avoid this, you would inject a service which wraps these file accesses. This allows you to replace the real service with a fake one during the unit tests. This fake service can provide hard coded test data for read accesses and dump written data to memory for later analysis or simply do nothing. Btw.: NSubstitute can create fake services at runtime easily.

The injection of services allows you to achieve Unit Test Isolation. E.g. you can test some logic without depending on correct file handling or database accesses or the correct functioning of other services. Injecting a service is just one way to do it. You could also just specify a method parameter as IEnumerable<string> with the content of the file instead. Events can also be used for decoupling. Instead of writing to a log, you could raise a log event.


Most DI frameworks allow you to specify the lifetime of objects. One of these options is Singleton, which means that the DI container will always return the same instance of a requested service. This allows you to wrap static classes in a service that behaves statically.

String answered 20/7, 2017 at 12:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.