Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# Dependency Container and constructors

I've spent some time documenting myself on dependency injection and IoC but I haven't found a solution to my problem.

My problem relates to the instantiation of objects when using a dependency container in the sense that it creates a dependency on the arguments of the constructor. In almost every examples I came across, the constructors of the concrete classes don't have any argument. It makes everything rather 'simple'. Hence my question here.

Let's take an example: I need to download some data from two sources A and B. Source A contains the data in various format; for example csv and xml. We don't need to specify such a thing for source B.

Here is some code (please note I simplified the code as much as I could to illustrate my point):

using System.Net;
using System.IO;
using System.Reflection;

namespace Question
{
  class Program
  {
    static void Main(string[] args)
    {
        //exemple of code using Client A
        DependencyContainer container1 = GetContainer1();
        IClient client1 = container1.Resolve<IClient>("xml");
        User user1 = new User(client1);
        user1.run();

        DependencyContainer container2 = GetContainer2();
        IClient client2 = container2.Resolve<IClient>();
        User user2 = new User(client2);
        user2.run();
    }

    public static DependencyContainer GetContainer1()
    {
        DependencyContainer container = new DependencyContainer();
        container.Register<IClient, ClientA>();
        return container;
    }

    public static DependencyContainer GetContainer2()
    {
        DependencyContainer container = new DependencyContainer();
        container.Register<IClient, ClientB>();
        return container;
    }
}

public class User
{
    private readonly IClient _Client;

    public User(IClient client)
    {
        _Client = client;
    }

    public void run()
    {
        string address = _Client.getAddress();
        string data = _Client.getData(address);
        _Client.writeData(data);
    }
}
// Abstraction
public interface IClient
{
    /// <summary>
    /// create the address or the name of the file storing the data
    /// </summary>
    string getAddress();

    /// <summary>
    /// uses a WebClient to go and get the data at the address indicated
    /// </summary>
    string getData(string adress);

    /// <summary>
    /// Write the data in a local folder
    /// </summary>
    void writeData(string data);
}

//Implementation A
public class ClientA : IClient
{
    // Specify the type of the file to be queried in the database
    // could be a csv or an xml for example
    private readonly string _FileType;

    public ClientA(string fileType)
    {
        _FileType = fileType;
    }

    public string getAddress()
    {
        return "addressOfFileContainingData." + _FileType;
    }

    public string getData(string address)
    {
        string data = string.Empty;
        using (WebClient client = new WebClient())
        {
            data = client.DownloadString(address);
        }
        return data;
    }

    public void writeData(string data)
    {
        string localAddress = "C:/Temp/";
        using (StreamWriter writer = new StreamWriter(localAddress))
        {
            writer.Write(data);
        }
    }
}

//Implementation B
public class ClientB : IClient
{
    public ClientB()
    {
    }

    public string getAddress()
    {
        return "addressOfFileContainingData";
    }

    public string getData(string address)
    {
        string data = string.Empty;
        using (WebClient client = new WebClient())
        {
            data = client.DownloadString(address);
        }
        return data;
    }

    public void writeData(string data)
    {
        string localAddress = "C:/Temp/";
        using (StreamWriter writer = new StreamWriter(localAddress))
        {
            writer.Write(data);
        }
    }
}

public class DependencyContainer
{
    private Dictionary<Type, Type> _Map = new Dictionary<Type, Type>();

    public void Register<TypeToResolve, ResolvedType>()
    {
        _Map.Add(typeof(TypeToResolve), typeof(ResolvedType));
    }

    public T Resolve<T>(params object[] constructorParameters)
    {
        return (T)Resolve(typeof(T), constructorParameters);
    }

    public object Resolve(Type typeToResolve, params object[] constructorParameters)
    {
        Type resolvedType = _Map[typeToResolve];
        ConstructorInfo ctorInfo = resolvedType.GetConstructors().First();
        object retObject = ctorInfo.Invoke(constructorParameters);
        return retObject;
    }
}

}

I tend to think there are some good points in this code, but feel free to correct me. However, the instantiations:

IClient client = container.Resolve<IClient>("xml");

and

IClient client = container.Resolve<IClient>();

draw a lot of concerns to me. The high level module (class User here) doesn't depend on the concrete implementation as expected. However, now the class Program depends on the structure of the constructor of the concrete class! So it solves one problem by creating a much bigger problem somewhere else. I would rather depend on the concrete implementation rather that on the structure of its constructor. Let's imagine the code of ClientA is refactored and the constructor is changed, then I have no idea that the class Program actually uses it.

Finally, my questions:

  1. have I missed the point of IoC?
  2. Am I miss-using it?
  3. If not, how to solve this issue?

One solution would be not to have any argument in constructor of ClientA. But does that mean that a constructor should never have any arguments when using a dependency container? Or does that mean that an object with arguments in its constructor is not a good candidate for this kind of technic? One could also argue that ClientA and ClientB shouldn't derive from the same single interface because they essentially don't behave in the same way.

Thank you for your comments and inputs.

like image 547
CDM Avatar asked Mar 14 '23 19:03

CDM


1 Answers

have I missed the point of IoC?

Yes and no. Fortunately, your concrete classes (User, ClientA, ClientB) all rely on Constructor Injection, which is the most important Dependency Injection (DI) pattern. DI Containers, on the other hand, are entirely optional.

Thus, using Pure DI, you'd simply implement your Main method like this:

static void Main(string[] args)
{
    //exemple of code using Client A
    User user1 =
       new User(
           new ClientA(
               "xml"));
    user1.run();

    User user2 =
        new User(
            new ClientB());
    user2.run();
}

Not only is this easy for everyone to understand, it also gives you compile-time feedback when you compose your object graphs.

The most important goal of DI is to ensure that the implementation code is appropriately decoupled, which is what Constructor Injection helps doing.

Am I miss-using it?

A bit, perhaps, but not much. If you wish to use a DI Container instead of Pure DI, you should follow the Register Resolve Release pattern. If you want a User object, you should request that, instead of requesting an IClient object:

var user = container.Resolve<User>();
user.run();

It's up to you to Register all services appropriately in the container. If you want to use ClientA, you'll need to tell the container which value it should use for the fileType constructor argument. Exactly how you do that depends on the specific DI Container you use.

Sometimes, however, you can define a convention for primitive dependencies, for example pulling all primitive values from the application's configuration file.

If not, how to solve this issue?

My recommendation would be to use the above Pure DI approach, unless you have a compelling reason to use a DI Container. In my experience, this rarely happens.

like image 194
Mark Seemann Avatar answered Mar 27 '23 12:03

Mark Seemann