user1548103 user1548103 - 2 months ago 9
C# Question

Create a list of custom objects with Inversion of Control paradigm

I want to adapt Dependency Injection and Inversion of Control into my daily development. Say I have an object type of

SomeObject
(implements the interface
ISomeObject
). I have a class which consumes this object called Data which implements the
IData
interface.

public interface ISomeObject {
int ID;
string Name;
bool IsAwesome;

void DoSomeStuffIfAwesome();
}

public Class SomeObject : ISomeObject {
int ID;
string Name;
bool IsAwesome;

void DoSomeStuffIfAwesome() { /*stuff happens here*/ }
}

public interface IData {
List<ISomeObject> GetSomeObjects();
}

public Class Data : IData {
List<ISomeObject> GetSomeObjects()
{
List<ISomeObject> objects = new List<ISomeObject>; // ??? Maybe and cast later?

//do some SQL stuff and get a SqlDataReader object called reader
while(reader.Read()) {
//ISomeObject someObj = ???
//Read into the someObj.ID, someObj.Name and someObj.IsAwesome fields
objects.add(someObj);
}
return objects;
}
}


The
GetSomeObjects()
method produces a list of
ISomeObject
objects. But I don't want
Data.cs
to have anything related to
SomeObject
hardcoded into it. I want some form of Dependency Injection to resolve the issue at runtime. What's the best way to handle this? I've considered the following:

1. Pass instance of
SomeObject
into
Data
's constructor.
This way I can get its type with
.GetType()
, store that into a private
System.Type
variable in
Data.cs
, and use
Activator.CreateInstance
in the loop to create new objects to be added to the list.
Data
would need to know about the
SomeObject
class specifically to cast, if I understand it correctly.

2. Pass an instance of my IoC container to
Data
's constructor
and just resolve the object type using
container.Resolve<ISomeObject>()
. This would make unit testing the
GetSomeObjects()
method difficult without utilizing my IoC container. I've read I shouldn't utilize the IoC container during unit testing, and should manually pass in what I need into methods.

3. Pass an
ISomeObject
object that has been instantiated as a
SomeObject
- I would then use that to create the object via some built in method, such as
SomeObject.GenerateList(IDataReader reader)
.

Answer

You can delegate the creation of the object out to something else;

public interface ISomeObjectFactory {
    ISomeObject Create(IDataReader reader);
}

which has a single responsibility to create instances of ISomeObject

using System.Collections.Generic;
using System.Data;

public interface IDbConnectionFactory {
    ///<summary>
    ///  Creates a connection based on the given database name or connection string.
    ///</summary>
    IDbConnection CreateConnection(string nameOrConnectionString);
}

public class Data : IData {
    private IDbConnectionFactory dbConnectionFactory;
    ISomeObjectFactory someObjectFactory;
    private string CONNECTION_STRING = "Connection string here";

    public Data(IDbConnectionFactory dbConnectionFactory, ISomeObjectFactory objectFactory) {
        this.dbConnectionFactory = dbConnectionFactory;
        this.someObjectFactory = objectFactory;
    }

    public List<ISomeObject> GetSomeObjects() {
        var objects = new List<ISomeObject>();
        //do some SQL stuff and return a data reader
        using (var connnection = dbConnectionFactory.CreateConnection(CONNECTION_STRING)) {
            using (var command = connnection.CreateCommand()) {
                //configure command to be executed.
                command.CommandText = "SELECT * FROM SOMEOBJECT_TABLE";
                connnection.Open();
                using (var reader = command.ExecuteReader()) {
                    while (reader.Read()) {
                        //...Logic to populate item
                        var someObject = someObjectFactory.Create(reader);
                        if (someObject != null)
                            objects.Add(someObject);
                    }
                }
            }
        }

        return objects;
    }
}

that way Data is only dependent on abstractions and not on concretions. Those can be determined/configured in the composition root at runtime.

Comments