实例化generics类型?

我目前正在研究generics集合类,我想创建一个从集合中返回对象的方法。 如果集合中不存在特定对象,则应创建对象,将其添加到集合中,然后返回。

我遇到了一些问题。 generics集合if表示抽象类的类型,我在实例化时遇到问题。

这是我的class级定义:

public class CacheCollection : List<CacheItem> where T : EntityBase 

这是我工作的部分完整的方法:

 public T Item(int Id) { CacheItem 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(entity); this.Add(result); } return result.Entity; } 

关于如何解决这个问题的任何想法?

编辑:从EntityBase派生的所有类都将Id作为只读属性。

更新2 :嗯,你在评论中说你没有定义非通用的CacheCollection类型; 但接着你说你有一个Dictionary 。 这些语句不可能都是真的,所以我猜通过CacheCollection你的意思是CacheCollection

现在问题是:如果X类型不是协变的,则X不能转换为X 。 也就是说,在您的情况下,仅仅因为T派生自EntityBase并不意味着CacheCollection派生自CacheCollection

为了具体说明原因,请考虑List类型。 假设您有一个List和一个Liststring派生自object ,但它不遵循List派生自List ; 如果确实如此,那么你可以拥有这样的代码:

 var strings = new List(); // If this cast were possible... var objects = (List)strings; // ...crap! then you could add a DateTime to a List! objects.Add(new DateTime(2010, 8, 23)); 

幸运的是,围绕这个(在我看来)的方式是非常简单的。 基本上,通过定义CacheCollection将派生非generics基类来配合我的原始建议。 更好的是,使用简单的非通用接口。

 interface ICacheCollection { EntityBase Item(int id); } 

(请查看下面的更新代码,了解如何在通用类型中实现此接口)。

然后,对于您的字典,而不是Dictionary> ,将其定义为Dictionary ,并将其余代码放在一起。


更新 :您似乎拒绝了我们! 所以你有一个非通用的CacheCollection基类, CacheCollection派生出来,是吗?

如果我对你对这个答案的最新评论的理解是正确的,那么这是我对你的建议。 编写一个类来提供对您的Dictionary的间接访问。 这样,您可以拥有许多CacheCollection实例,而不会牺牲类型安全性。

像这样的东西( 注意:根据上面的更新修改代码 ):

 class GeneralCache { private Dictionary _collections; public GeneralCache() { _collections = new Dictionary(); } public T GetOrAddItem(int id, Func factory) where T : EntityBase { Type t = typeof(T); ICacheCollection collection; if (!_collections.TryGetValue(t, out collection)) { collection = _collections[t] = new CacheCollection(factory); } CacheCollection stronglyTyped = (CacheCollection)collection; return stronglyTyped.Item(id); } } 

这将允许您编写如下代码:

 var cache = new GeneralCache(); RedEntity red = cache.GetOrAddItem(1, id => new RedEntity(id)); BlueEntity blue = cache.GetOrAddItem(2, id => new BlueEntity(id)); 

好吧,如果T派生自EntityBase但没有无参数构造函数,那么最好的办法是指定一个工厂方法,它将为CacheCollection构造函数中的相应参数生成一个T

像这样( 注意:根据上面的更新修改代码 ):

 public class CacheCollection : List>, ICacheCollection where T : EntityBase { private Func _factory; public CacheCollection(Func factory) { _factory = factory; } // Here you can define the Item method to return a more specific type // than is required by the ICacheCollection interface. This is accomplished // by defining the interface explicitly below. public T Item(int id) { // Note: use FirstOrDefault, as First will throw an exception // if the item does not exist. CacheItem result = this.Where(t => t.Entity.Id == id) .FirstOrDefault(); if (result == null) //item not yet in cache, load it! { T entity = _factory(id); // Note: it looks like you forgot to instantiate your result variable // in this case. result = new CacheItem(entity); Add(result); } return result.Entity; } // Here you are explicitly implementing the ICacheCollection interface; // this effectively hides the interface's signature for this method while // exposing another signature with a more specific return type. EntityBase ICacheCollection.Item(int id) { // This calls the public version of the method. return Item(id); } } 

我还建议,如果您的项目将具有唯一ID,则使用Dictionary>作为您的后备存储而不是List>因为它将使您的项目查找O( 1)代替O(N)。

(我还建议使用私有成员来实现此类来保存集合本身而不是直接从集合inheritance,因为使用inheritance公开了您可能想要隐藏的function,例如AddInsert等)

目前没有什么能阻止你(或其他人)声明CacheCollection的实例,其中T是无法直接创建的东西(例如,如果T代表抽象类)。 在错误提示时,您可以通过将new()约束添加到generics参数来要求T为“可创建”:

 public class CacheCollection : List> where T : EntityBase, new() 

但是你仍然会遇到问题:没有办法告诉C#编译器T将有一个接受int类型参数的构造函数。 围绕该问题最简单的方法可能是创建T的实例,然后分配其Id属性(假设EntityBase定义了这样的属性):

 T entity = new T { Id = Id }; 

我相信你得到的编译器错误告诉你解决方案。 您必须添加另一个类型约束,以便它知道您可以创建实例。 你需要做的就是添加new()

 public class CacheCollection : List> where T : EntityBase, new() 

但这只允许您使用默认构造函数或无参数构造函数创建实例。 因此,您需要在EntityBase上指定一个属性,以允许您通过添加另一个约束来设置所需的值或要使用的接口。

就像是:

 public class CacheCollection : List> where T : EntityBase, new(), IIdSetter 

问题是无法保证EntityBase的子节点具有所需的构造函数。

您还可以使用reflection来查找和调用构造函数:

 var constructor = entitType.GetConstructor(new Type[] { typeof(int) }); return (EntityBase)constructor.Invoke(new object[] { id }); 

但是,除非您跟上所有子类实现,否则无法在运行时保证这一点。

两件事情:

 // add new() to your where clause: public class CacheCollection : List> where T : EntityBase, new() // change the creation of the object: ... T entity = new T(); entity.Id = Id; ... 

我在类似情况下所做的是创建一个IId接口

 public interface IId { public Id { get; set; } } 

通过使用部分类很容易将接口声明添加到实体类:

 public partial MyClass : IId { } 

您的类定义现在变为:

 public class CacheCollection : List> where T : EntityBase, IId, new() 
 T entity = (T)Activator.CreateInstance(typeof(T), Id);