所以我试图在界面中创建一个属性列表,如下所示:(仅为示例使用1个属性)
public interface IRepository
{
IEnumerable<Budget> Budgets { get; set; }
}
但我希望能够使用IEnumerable的不同实现来创建此实现。例如,我希望能够实现IRepository并将'Budgets'作为List或DBSet(或者我制作的任何其他自定义类来实现IEnumberable)
我希望我能做到这样的事情:
public interface IRepository<T>
{
// Database
T<Budget> Budgets { get; set; }
}
或
public interface IRepository<T>
where T : IEnumerable<>
{
// Database
T<Budget> Budgets { get; set; }
}
然后我可以像这样实现它:
public class Repository : IRepository<List>
{
List<Budget> Budgets { get; set; }
}
但它的语法无效。
这是否可能,如果没有,我可能有什么替代方案?
答案 0 :(得分:1)
如果您只想支持Budget
,那么您可以使用第二个示例
public interface IRepository<T>
where T : IEnumerable<Budget>
{
// Database
T Budgets { get; set; }
}
如果你想支持的不只是Budget
作为内部类型,你需要使用两种泛型类型
public interface IRepository<T, U>
where T : IEnumerable<U>
{
// Database
T Budgets { get; set; }
}
答案 1 :(得分:0)
但我希望能够使用IEnumerable的不同实现创建此实现。
这正是您的代码已经允许的内容。
public interface IRepository
{
IEnumerable<Budget> Budgets { get; set; }
}
此接口的各种实现可以返回IEnumerable<Budget>
的不同实现。
这是一种接近它的方法。如果一个类依赖于一个存储库,那么从该类依赖于它的角度编写该接口。这样,该类不仅仅是采用任何可用的接口。它“拥有”界面。它说它需要什么。
最终的具体实施取决于技术决策。您可能有一个返回DBSet<Budget>
的后端。在这种情况下,具体实现将返回。
最后,写一个适配器。它实现了消费者所需的接口。它调用具体的存储库并返回消费者需要的任何内容。
这听起来像是更多的代码(而且它是),但这就是你所完成的。
答案 2 :(得分:0)
这个例子适合我。您可以为可以从数组中填充的任何IEnumerable
提供类型参数。
构造具体的IEnumerable
并填充它有点棘手,因为IEnumerable
缺少任何添加到集合的方法,但我通过使用带有数组的构造函数来解决它。许多类型都有它。
我的示例使用List<Budget>
和ConcurrentQueue<Budget>
执行两项测试,并且工作正常。
我不知道你怎么可能计划使用这个东西来生成没有构造函数的IEnumerable
类型。例如,如果你想要Dictionary<Budget>
,那么工厂(只运行通用代码)可能知道如何填充密钥?看起来像某个地方你需要一些非通用的代码。
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));
}
}
}
答案 3 :(得分:0)
如果目标是能够获得相同的数据,但是在各种数据结构中,我是否可以建议您使用一些控制反转并让调用者注入结构?这样他也可以提供自己的自定义数据结构。
例如:
public void GetBudgets<T>(T collection) where T : ICollection<Budget>
{
Db.GetBudgetData().ToList().ForEach(a => collection.Add(a));
}
您可以致电:
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>>
或者:
var r = new Repository();
var list = new CustomCollection<Budget>();
r.GetBudgets(list);
为方便调用者,您还可以提供不需要注入的通用方法,因此当不需要自定义集合时,调用者可以使用较短的代码:
public T GetBudgets<T>() where T : ICollection<Budget>, new()
{
T collection = new T();
GetBudgets(collection);
return collection;
}
可以用一行调用:
var budgets = repository.GetBudgets<List<Budget>>();
使用这种方法使调用者非常容易。他们将不再需要为他们想要使用的每种不同数据类型实例化新的Repository<T>
;他们只需要调用该方法。而且你不再需要弄清楚如何实现IRepository<T>
,从而完全避免这个问题。
如果你需要支持ICollection
后代以外的数据类型,你需要一个单独的方法,因为数据布局是如此不同(例如DataTable有列,你需要选择哪里去哪里)通用代码是不可能的(Add
并非普遍适用于所有IEnumerable
类型)。但是您不需要单独的课程,只需使用prototype overload。