Reputation: 295
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
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
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
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.
Upvotes: 0
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