Luke Bourne
Luke Bourne

Reputation: 295

Using a Generic Class as a type parameter in another generic class

I'm trying to create a list of properties in an interface like so: (just using 1 property for the example)

public interface IRepository
{
    IEnumerable<Budget> Budgets { get; set; }
}

But I want to be able to create Implementations of this with different implementations of IEnumerable<T>. So for example, I want to be able to implement IRepository and have 'Budgets' as a list, or a DbSet (or any other custom class i make that implements IEnumerable<T>)

I was hoping I could do something like this:

public interface IRepository<T>
{
    // Database
    T<Budget> Budgets { get; set; }
}

or

public interface IRepository<T>
where T : IEnumerable<>
{
    // Database
    T<Budget> Budgets { get; set; }
}

Then I could implement it like so:

public class Repository : IRepository<List>
{
    List<Budget> Budgets { get; set; }
}

but its not valid syntax.

Is this even possible and if not what alternatives do I possibly have?

Upvotes: 2

Views: 1589

Answers (4)

John Wu
John Wu

Reputation: 52290

If the goal is to be able to get the same data but in a variety of data structures, may I suggest you use a bit of inversion of control and have the caller inject the structure? That way he could supply his own custom data structure as well.

For example:

public void GetBudgets<T>(T collection) where T : ICollection<Budget>
{
    Db.GetBudgetData().ToList().ForEach(a => collection.Add(a));
}

Which you would call with:

var r = new Repository();
var list = new System.Collections.Generic.HashSet<Budget>(); //Or any ICollection
r.GetBudgets(list);  //Type is inferred, so no need for <HashSet<Budget>>

Or:

var r = new Repository();
var list = new CustomCollection<Budget>(); 
r.GetBudgets(list);

As a convenience to the caller, you can also supply a generic method that does not require an injection, so the caller can use a shorter piece of code when a custom collection is not needed:

public T GetBudgets<T>() where T : ICollection<Budget>, new()
{
    T collection = new T();
    GetBudgets(collection);
    return collection;
}

Which can be called with one line:

var budgets = repository.GetBudgets<List<Budget>>();

Using this approach makes it very easy for the caller. They would no longer have to instantiate a new Repository<T> for each different data type they want to work with; they just need to call the method. And you no longer have to figure out how to implement IRepository<T>, thus avoiding the problem entirely.

If you need to support a data type other than a descendent of ICollection, you will need a separate method, because the data layout is so different (e.g. a DataTable has columns, you need to pick what goes where) that generic code is not possible (Add is not universally available for all IEnumerable types). But you don't need a separate class, you can just use a prototype overload.

Upvotes: 0

John Wu
John Wu

Reputation: 52290

This example works for me. You can supply a type argument for any IEnumerable that can be populated from an array.

It's a little tricky to construct a concrete IEnumerable and populate it, since IEnumerable lacks any methods for adding to the collection, but I got around it by using a constructor that takes an array. Many of the types have it. My example executes two tests, using a List<Budget> and a ConcurrentQueue<Budget>, and works fine.

I don't know how you possibly plan to use this thing to generate types of IEnumerable that don't have a constructor that takes an array. For example, if you wanted Dictionary<Budget>, how would the factory (which runs only generic code) possibly know how to populate the keys? Seems like somewhere along the line you're going to need some non-generic code for that.

public class Budget
{
    public string Name { get; set; }
}

public interface IRepository<T> where T : class, IEnumerable<Budget>, new()
{

    T Budgets { get; }
}

public class Repository<T> : IRepository<T> where T : class, IEnumerable<Budget>, new() 
{
    public T Budgets
    {
        get
        {
            Budget[] data = GetData();
            var c = typeof(T).GetConstructor(new [] {typeof(Budget[])});
            object o = c.Invoke(new object[] { data });

            return o as T;
        }
    }

    private Budget[] GetData()
    {
        var a = new Budget[2];
        a[0] = new Budget { Name = "Hello world!" };
        a[1] = new Budget { Name = "Goodbye world!" };

        return a;
    }
}

class Example
{
    static public void TestList()
    {

        Repository<List<Budget>> r = new Repository<List<Budget>>();
        var list = r.Budgets;

        foreach (var b in list)
        {
            Console.WriteLine(String.Format("{0}", b.Name));
        }
    }
    static public void TestQueue()
    {

        Repository<ConcurrentQueue<Budget>> r = new Repository<ConcurrentQueue<Budget>>();
        var list = r.Budgets;

        foreach (var b in list)
        {
            Console.WriteLine(String.Format("{0}", b.Name));
        }
    }
}

Upvotes: 0

Scott Hannen
Scott Hannen

Reputation: 29302

But I want to be able to create Implementations of this with different implementations of IEnumerable.

That's exactly what your code already allows.

public interface IRepository
{
    IEnumerable<Budget> Budgets { get; set; }
}

Various implementations of this interface can return different implementations of IEnumerable<Budget>.

Here's a way to approach it. If a class is going to depend on a repository, write the interface from the perspective of the class that will depend on it. That way the class isn't just taking whatever interface is available. It "owns" the interface. It says what it needs.

The final, concrete implementation is dictated by technical decisions. You might have a back-end that returns a DBSet<Budget>. In that case that's what the concrete implementation will return.

Finally, write an adapter. It implements the interface required by the consumer. It calls the concrete repository and returns whatever it is that the consumer needs.

It sounds like more code (and it is) but here's what you accomplish.

  • Your application logic is decoupled from the concrete implementation of the repository.
  • You'll only write the code that you need. Instead of writing numerous alternate versions of the concrete repository, you'll only write an adapter when you need it. So compared to the initial approach it will be less code.

Upvotes: 0

Scott Chamberlain
Scott Chamberlain

Reputation: 127603

If you only want to support Budget then you had it very close with 2nd example

public interface IRepository<T>
where T : IEnumerable<Budget>
{
    // Database
    T Budgets { get; set; }
}

If you want to support more than just Budget as the inner type you need to use two generic types

public interface IRepository<T, U>
where T : IEnumerable<U>
{
    // Database
    T Budgets { get; set; }
}

Upvotes: 1

Related Questions