更新2: 你在评论中说你没有定义一个非泛型的CacheCollection
类型,但是你接着又说你有一个Dictionary<Type, CacheCollection>
。这两个陈述不能同时成立,所以我猜你指的是CacheCollection<EntityBase>
。
现在问题来了:如果X<T>
类型不是协变的,那么X<Derived>
就无法转换为X<Base>
。也就是说,在你的情况下,仅仅因为T
派生自EntityBase
并不意味着CacheCollection<T>
派生自CacheCollection<EntityBase>
。
为了具体说明这一点,考虑List<T>
类型。假设你有一个List<string>
和一个List<object>
。string
派生自object
,但这并不意味着List<string>
派生自List<object>
;如果是这样的话,那么你就可以写出这样的代码:
var strings = new List<string>();
var objects = (List<object>)strings;
objects.Add(new DateTime(2010, 8, 23));
幸运的是,我认为解决这个问题的方法非常简单。基本上,按照我的最初建议,定义一个非通用的基类,从中派生CacheCollection<T>
。更好的做法是使用一个简单的非通用接口。
interface ICacheCollection
{
EntityBase Item(int id);
}
请看下面更新过的代码,以了解如何在通用类型中实现该接口。
然后对于您的字典,不要使用`Dictionary >`,而是定义为`Dictionary `,您的其余代码应该就能组合起来了。
更新:看来你一直没有告诉我们全部信息!所以您有一个非泛型的CacheCollection基类,其中CacheCollection派生自该基类,是吗?
如果我对您最新评论的理解是正确的,那么我的建议是编写一个类以间接访问您的这个Dictionary。这样,您可以拥有许多CacheCollection实例,而不会牺牲类型安全。
像这样(注意:根据上面的
new更新修改的代码)
class GeneralCache
{
private Dictionary<Type, ICacheCollection> _collections;
public GeneralCache()
{
_collections = new Dictionary<Type, ICacheCollection>();
}
public T GetOrAddItem<T>(int id, Func<int, T> factory) where T : EntityBase
{
Type t = typeof(T);
ICacheCollection collection;
if (!_collections.TryGetValue(t, out collection))
{
collection = _collections[t] = new CacheCollection<T>(factory);
}
CacheCollection<T> stronglyTyped = (CacheCollection<T>)collection;
return stronglyTyped.Item(id);
}
}
这将允许您编写如下代码:
这段代码示例:
var cache = new GeneralCache();
RedEntity red = cache.GetOrAddItem<RedEntity>(1, id => new RedEntity(id));
BlueEntity blue = cache.GetOrAddItem<BlueEntity>(2, id => new BlueEntity(id));
如果 T
派生自 EntityBase
但没有无参构造函数,你最好的选择是指定一个工厂方法,在你的 CacheCollection<T>
构造函数中生成适当参数的 T
。
像这样 (注意:代码根据上面的新更新进行了修改):
public class CacheCollection<T> : List<CacheItem<T>>, ICacheCollection where T : EntityBase
{
private Func<int, T> _factory;
public CacheCollection(Func<int, T> factory)
{
_factory = factory;
}
public T Item(int id)
{
CacheItem<T> result = this.Where(t => t.Entity.Id == id)
.FirstOrDefault();
if (result == null)
{
T entity = _factory(id);
result = new CacheItem<T>(entity);
Add(result);
}
return result.Entity;
}
EntityBase ICacheCollection.Item(int id)
{
return Item(id);
}
}
如果您的项目将拥有独特的ID,我建议您使用
Dictionary<int, CacheItem<T>>
作为支持存储,而不是
List<CacheItem<T>>
。这样可以使您的项目查找时间复杂度从O(N)降至O(1)。
此外,我还建议使用私有成员来保存集合本身,而不是直接继承集合。因为继承会暴露您可能想要隐藏的功能,比如
Add
,
Insert
等。
EntityBase
继承并带有一个接受int
参数的构造函数的类型CustomEntity
。那么你可以通过编写var cache = new CacheCollection<CustomEntity>(id => new CustomEntity(id));
来创建这些实体的集合。 - Dan Tao