[英]Using a Generic Class as a type parameter in another generic class
我正在尝试在界面中创建一个属性列表,如下所示:(仅使用 1 个属性作为示例)
public interface IRepository
{
IEnumerable<Budget> Budgets { get; set; }
}
但我希望能够使用IEnumerable<T>
不同实现来创建此实现。 例如,我希望能够实现IRepository
并将“预算”作为列表或DbSet
(或我创建的任何其他实现IEnumerable<T>
自定义类)
我希望我能做这样的事情:
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; }
}
但它的语法无效。
这甚至可能吗,如果不是,我可能有什么选择?
如果您只想支持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; }
}
但是我希望能够使用不同的 IEnumerable 实现来创建这个实现。
这正是您的代码已经允许的。
public interface IRepository
{
IEnumerable<Budget> Budgets { get; set; }
}
此接口的各种实现可以返回IEnumerable<Budget>
不同实现。
这是接近它的方法。 如果类将依赖于存储库,请从依赖它的类的角度编写接口。 这样,该类不只是采用任何可用的接口。 它“拥有”接口。 它说它需要什么。
最终的具体实施取决于技术决策。 您可能有一个返回DBSet<Budget>
的DBSet<Budget>
。 在那种情况下,这就是具体实现将返回的内容。
最后,编写一个适配器。 它实现了消费者所需的接口。 它调用具体的存储库并返回消费者需要的任何内容。
这听起来像是更多的代码(确实如此),但这就是您要完成的工作。
这个例子对我有用。 您可以为可以从数组填充的任何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));
}
}
}
如果目标是能够获得相同的数据但采用多种数据结构,我是否建议您使用一点控制反转并让调用者注入该结构? 这样他也可以提供他自己的自定义数据结构。
例如:
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
类型)。 但是您不需要单独的类,您可以只使用原型重载。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.