代码之家  ›  专栏  ›  技术社区  ›  Sonny Boy

实例化泛型类型?

  •  3
  • Sonny Boy  · 技术社区  · 14 年前

    不过,我遇到了一些问题。泛型集合if属于表示抽象类的类型,而我在实例化它时遇到了问题。

    以下是我的类定义:

    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作为只读属性。

    6 回复  |  直到 11 年前
        1
  •  7
  •   Dan Tao    14 年前

    :嗯,你在评论中说你没有定义非泛型 CacheCollection 但是你接着说你有一个 Dictionary<Type, CacheCollection> . 这些说法不可能都是真的,所以我猜 缓存集合 CacheCollection<EntityBase> .

    现在问题来了:a X<Derived> X<Base> X<T> 类型 is not covariant . 对你来说,就是因为 T 派生自 EntityBase CacheCollection<T> .

    要具体说明原因,请考虑 List<T> List<string> 和一个 List<object> . string 派生自 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));
    

    幸运的是,解决这个问题的方法(在我看来)非常简单。基本上,通过定义 非泛型基类 缓存集合<T> 将导出

    interface ICacheCollection
    {
        EntityBase Item(int id);
    }
    

    (看看我下面的更新代码,看看如何在泛型类型中实现这个接口)。

    Dictionary<Type, CacheCollection<EntityBase>> ,将其定义为 Dictionary<Type, ICacheCollection> 剩下的代码应该放在一起。


    更新 :看来你在隐瞒我们!所以你有一个非通用的 基类 缓存集合<T> 哦,我说得对吗?

    如果我对你对这个答案的最新评论的理解是正确的,这是我给你的建议。编写一个类来提供对此的间接访问 字典<类型,缓存集合> 你的。这样你可以有很多 缓存集合<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型 但是没有无参数构造函数,最好的办法是指定一个工厂方法来生成 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;
        }
    
        // 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);
        }
    }
    

    Dictionary<int, CacheItem<T>> 作为你的后备商店而不是 List<CacheItem<T>> 因为它将使您的项目查找O(1)而不是O(N)。

    Add , Insert 等)

        2
  •  0
  •   Daniel Pratt    14 年前

    目前没有任何东西阻止您(或其他人)声明 CacheCollection<T> T 是不能直接创建的(如果 T型 例如,表示一个抽象类)。根据错误提示,您可以要求 T型 通过添加 new()

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

    不过,您仍然会遇到一个问题:无法告诉C编译器 将有一个接受类型为的参数的构造函数 int T型 Id 财产(假设 EntityBase 定义这样的属性):

    T entity = new T { Id = Id };
    
        3
  •  0
  •   Sean Copenhaver    14 年前

    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 });
    

        4
  •  0
  •   davehauser    14 年前

    两件事:

    // 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;
    ...
    
        5
  •  0
  •   Jeroen Huinink    14 年前

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

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

    public partial MyClass : IId { }
    

    您的类定义现在变成:

    public class CacheCollection<T> : List<CacheItem<T>> where T : EntityBase, IId, new()
    
        6
  •  0
  •   Mark H    14 年前
    T entity = (T)Activator.CreateInstance(typeof(T), Id);