我目前正在研究泛型集合类,我想创建一个从集合中返回对象的方法。如果集合中不存在特定对象,则应创建对象,将其添加到集合中,然后返回。
我遇到了一些问题。泛型集合,如果是一个表示抽象类的类型,我在实例化时遇到了麻烦。
这是我的班级定义:
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作为只读属性。
答案 0 :(得分:7)
更新2 :您在评论中说您尚未定义非通用CacheCollection
类型;但接着你说你有一个Dictionary<Type, CacheCollection>
。这些陈述不可能都是真的,所以我猜CacheCollection
你的意思是CacheCollection<EntityBase>
。
现在问题是:如果X<Derived>
类型is not covariant,则X<Base>
无法投放到X<T>
。也就是说,在您的情况下,仅仅因为T
派生自EntityBase
并不意味着CacheCollection<T>
派生自CacheCollection<EntityBase>
。
为了具体说明原因,请考虑List<T>
类型。假设您有List<string>
和List<object>
。 string
来自object
,但不是List<string>
来自List<object>
;如果确实如此,那么你可以使用这样的代码:
var strings = new List<string>();
// If this cast were possible...
var objects = (List<object>)strings;
// ...crap! then you could add a DateTime to a List<string>!
objects.Add(new DateTime(2010, 8, 23));
幸运的是,围绕这个(在我看来)的方式非常简单。基本上,通过定义非通用基类CacheCollection<T>
将派生,继续我的原始建议。更好的是,使用简单的非通用接口。
interface ICacheCollection
{
EntityBase Item(int id);
}
(请查看下面的更新代码,了解如何在通用类型中实现此界面。)
然后,对于您的字典而不是Dictionary<Type, CacheCollection<EntityBase>>
,将其定义为Dictionary<Type, ICacheCollection>
,其余代码应该合并在一起。
更新:您似乎拒绝了我们!所以你有一个CacheCollection
派生的非通用CacheCollection<T>
基类,我是对的吗?
如果我对你对这个答案的最新评论的理解是正确的,这是我对你的建议。编写一个类来提供对此Dictionary<Type, CacheCollection>
个人的间接访问。这样,您可以拥有多个CacheCollection<T>
个实例而不会牺牲类型安全性。
这样的事情(注意:代码根据上面的新更新修改):
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
但没有无参数构造函数,那么最好的办法是指定一个工厂方法,为适当的方法生成T
CacheCollection<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;
}
// 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<T> 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<T>(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<int, CacheItem<T>>
作为后备商店而不是List<CacheItem<T>>
,因为它会使您的商品查找O(1)而不是O(N)。
(我还建议使用私有成员来实现此类来保存集合本身而不是直接从集合继承,因为使用继承公开了您可能想要隐藏的功能,例如Add
,Insert
等等。)
答案 1 :(得分:0)
目前没有任何东西可以阻止你(或其他人)声明CacheCollection<T>
的实例,其中T
是无法直接创建的(如果T
代表抽象类,例)。如错误提示,您可以通过将T
约束添加到通用参数来要求new()
为'可创建':
public class CacheCollection<T> : List<CacheItem<T>> where T : EntityBase, new()
但是你仍然会遇到问题:没有办法告诉C#编译器T
将有一个接受int
类型参数的构造函数。围绕该问题最简单的方法可能是创建T
的实例,然后分配其Id
属性(假设EntityBase
定义了这样的属性):
T entity = new T { Id = Id };
答案 2 :(得分:0)
我相信你得到的编译器错误告诉你解决方案。您必须添加另一个类型约束,以便它知道您可以创建实例。您需要做的就是添加new()
public class CacheCollection<T> : List<CacheItem<T>> where T : EntityBase, new()
但这只允许您使用默认构造函数或无参数构造函数创建实例。因此,您需要在EntityBase上指定一个属性,以允许您通过添加另一个约束来设置所需的值或要使用的接口。
类似的东西:
public class CacheCollection<T> : List<CacheItem<T>> where T : EntityBase, new(), IIdSetter
问题是无法保证EntityBase的子节点具有所需的构造函数。
您也可以使用反射来查找和调用构造函数:
var constructor = entitType.GetConstructor(new Type[] { typeof(int) });
return (EntityBase)constructor.Invoke(new object[] { id });
但是,除非您跟上所有子类实现,否则无法在运行时保证这一点。
答案 3 :(得分:0)
两件事:
// add new() to your where clause:
public class CacheCollection<T> : List<CacheItem<T>> where T : EntityBase, new()
// change the creation of the object:
...
T entity = new T();
entity.Id = Id;
...
答案 4 :(得分:0)
我在类似情况下所做的是创建一个IId接口
public interface IId
{
public Id { get; set; }
}
通过使用分部类很容易将接口声明添加到实体类中:
public partial MyClass : IId { }
您的班级定义现在变为:
public class CacheCollection<T> : List<CacheItem<T>> where T : EntityBase, IId, new()
答案 5 :(得分:0)
T entity = (T)Activator.CreateInstance(typeof(T), Id);