我目前正在研究一个通用集合类,我想创建一个从集合中返回对象的方法。如果集合中不存在特定对象,则应创建该对象,将其添加到集合中,然后返回。
不过我遇到了一些问题。通用集合如果属于代表抽象类的类型,并且我在实例化它时遇到问题。
这是我的类定义:
public class CacheCollection<T> : List<CacheItem<T>> where T : EntityBase
这是我正在研究的部分完整的方法:
public T Item(int Id)
{
CacheItem<T> result = this.Where(t => t.Entity.Id == Id).First();
if (result == null) //item not yet in cache, load it!
{
T entity = new T(Id); //design time exception here: Cannot create an instance of the variable type 'T' because it does not have the new() constraint
result = new CacheItem<T>(entity);
this.Add(result);
}
return result.Entity;
}
关于如何解决这个问题有什么想法吗?
编辑:从 EntityBase 派生的所有类都将 Id 作为只读属性。
I'm currently working on a generic collection class and I'd like to create a method which returns an object from the collection. If the specific object does not exist in the collection then the object should be created, added to the collection, and then returned.
I'm encountering a few problems though. The generic collection if of a type which represents an abstract class and I'm having trouble instantiating that.
Here's my class definition:
public class CacheCollection<T> : List<CacheItem<T>> where T : EntityBase
And here's the partially complete method on which I am working:
public T Item(int Id)
{
CacheItem<T> result = this.Where(t => t.Entity.Id == Id).First();
if (result == null) //item not yet in cache, load it!
{
T entity = new T(Id); //design time exception here: Cannot create an instance of the variable type 'T' because it does not have the new() constraint
result = new CacheItem<T>(entity);
this.Add(result);
}
return result.Entity;
}
Any ideas on how to get around this?
EDIT: All classes derived from EntityBase have Id as a read-only property.
发布评论
评论(6)
更新2:嗯,你在评论中说你还没有定义非泛型
CacheCollection
类型;但你接着说你有一个Dictionary
。这些陈述不可能同时成立,因此我猜测CacheCollection
您的意思是CacheCollection
。现在的问题是:如果 无法转换为 。也就是说,在您的情况下,仅仅因为
X
类型为 XX
="http://msdn.microsoft.com/en-us/library/dd799517.aspx" rel="nofollow noreferrer">不是协变的T
派生自EntityBase
并不意味着CacheCollection
派生自CacheCollection ;
。要具体说明其原因,请考虑
List
类型。假设您有一个List
和一个List
幸运的是,解决这个问题的方法(在我看来)非常简单。基本上,遵循我最初的建议,定义一个非泛型基类,
CacheCollection
将从该基类派生。更好的是,使用简单的非通用界面。(请查看下面我更新的代码,了解如何在泛型类型中实现此接口)。
然后,对于您的字典,将其定义为
Dictionary
,而不是Dictionary>
,其余代码应该一起来吧。更新:看来您对我们隐瞒了!因此,您有一个非泛型 CacheCollection 基类,
CacheCollection
派生自该基类,对吗?如果我对您对此答案的最新评论的理解是正确的,那么这是我给您的建议。编写一个类来提供对您的此
Dictionary
的间接访问。这样您就可以拥有许多CacheCollection
实例,而无需牺牲类型安全性。像这样的东西(注意:根据上面的新更新修改的代码):
这将允许您编写如下代码:
好吧,如果 构造函数。
T
派生自EntityBase
但没有无参数构造函数,您最好的选择是指定一个工厂方法,该方法将为T
中的适当参数生成T
code>CacheCollection像这样(注意:根据上面的新更新修改的代码):
如果您的项目将具有唯一的 ID,我还建议使用
Dictionary< ;int, CacheItem>
作为您的后备存储,而不是List>
因为它将使您的项目查找 O(1) 而不是 O( N)。(我还建议使用私有成员来保存集合本身来实现此类,而不是直接从集合继承,因为使用继承会公开您可能想要隐藏的功能,例如
Add
、Insert< /代码>等)
UPDATE 2: Well, you say in a comment that you have not defined a non-generic
CacheCollection
type; but then you go on to say that you have aDictionary<Type, CacheCollection>
. These statements cannot both be true, so I am guessing that byCacheCollection
you meanCacheCollection<EntityBase>
.Now here's the problem: a
X<Derived>
cannot be cast to aX<Base>
if theX<T>
type is not covariant. That is, in your case, just becauseT
derives fromEntityBase
does not mean thatCacheCollection<T>
derives fromCacheCollection<EntityBase>
.For a concrete illustration of why this is, consider the
List<T>
type. Say you have aList<string>
and aList<object>
.string
derives fromobject
, but it does not follow thatList<string>
derives fromList<object>
; if it did, then you could have code like this:Fortunately, the way around this (in my view) is pretty straightforward. Basically, go with my original suggestion by defining a non-generic base class from which
CacheCollection<T>
will derive. Better yet, go with a simple non-generic interface.(Take a look at my updated code below to see how you can implement this interface in your generic type).
Then for your dictionary, instead of a
Dictionary<Type, CacheCollection<EntityBase>>
, define it as aDictionary<Type, ICacheCollection>
and the rest of your code should come together.UPDATE: It seems that you were withholding from us! So you have a non-generic
CacheCollection
base class from whichCacheCollection<T>
derives, am I right?If my understanding of your latest comment to this answer is correct, here's my advice to you. Write a class to provide indirect access to this
Dictionary<Type, CacheCollection>
of yours. This way you can have manyCacheCollection<T>
instances without sacrificing type safety.Something like this (note: code modified based on new update above):
This would allow you to write code like the following:
Well, if
T
derives fromEntityBase
but does not have a parameterless constructor, your best bet is going to be to specify a factory method that will generate aT
for the appropriate parameters in yourCacheCollection<T>
constructor.Like this (note: code modified based on new update above):
I would also recommend, if your items are going to have unique IDs, to use a
Dictionary<int, CacheItem<T>>
as your backing store instead of aList<CacheItem<T>>
as it will make your item lookup O(1) instead of O(N).(I would also recommend implementing this class using a private member to hold the collection itself rather than inheriting from the collection directly, as using inheritance exposes functionality you probably want hidden such as
Add
,Insert
, etc.)目前,没有什么可以阻止您(或其他人)声明
CacheCollection
实例,其中T
是无法直接创建的内容(如果T<例如,/code> 代表一个抽象类)。正如错误提示,您可以通过将
new()
约束添加到通用参数来要求T
是“可创建的”:您仍然会遇到问题,不过:无法告诉 C# 编译器
T
将有一个接受int
类型参数的构造函数。解决该问题的最简单方法可能是创建一个T
实例,然后分配其Id
属性(假设EntityBase
定义了这样的属性) :Presently there is nothing preventing you (or someone else) from declaring an instance of
CacheCollection<T>
whereT
is something that cannot be directly created (ifT
represented an abstract class, for example). As the error hints, you can require thatT
be 'creatable' by adding thenew()
constraint to the generic parameter:You're still going to have a problem, though: There is no way to tell the C# compiler that
T
will have a constructor that accepts a parameter of typeint
. Probably the easiest way around that issue is to create an instance ofT
and then assign itsId
property (assuming thatEntityBase
defines such a property):我相信您收到的编译器错误会告诉您解决方案。您必须添加另一个类型约束,以便它知道您可以创建实例。您需要做的就是添加
new()
但这只允许您使用默认或无参数构造函数创建实例。因此,您需要在 EntityBase 上指定一个属性以允许您设置所需的值,或者通过添加另一个约束来指定要使用的接口。
类似这样的:
问题是不能保证 EntityBase 的子级具有您想要的构造函数。
您还可以使用反射来查找并调用构造函数:
但是,除非您跟上所有子类的实现,否则在运行时无法保证这一点。
I believe the compiler error you are getting tells you the solution. You have to add another type constraint so it knows you can create an instance. All you need to do is add the
new()
But this will only allow you to create an instance with the default or a parameterless constructor. So you will need either to specify a property on the EntityBase to allow you to set the values you need to or an interface to use by adding another constraint.
Something like:
The problem is that a child of EntityBase can't be guaranteed to have the constructor you want.
Also you can use reflection to find and call the constructor:
But again this can't be guaranteed at runtime unless you keep up with all your child class implementations.
两件事:
Two things:
我在类似情况下所做的是创建一个 IId 接口
通过使用分部类可以轻松地将接口声明添加到实体类中:
您的类定义现在变为:
What I did in similar situations is create an IId interface
It is easy to add the interface declaration to your entity classes through the use of partial classes:
Your class definition now becomes: