实例化泛型类型?

发布于 2024-09-15 19:36:34 字数 817 浏览 3 评论 0 原文

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

不过我遇到了一些问题。通用集合如果属于代表抽象类的类型,并且我在实例化它时遇到问题。

这是我的类定义:

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

西瑶 2024-09-22 19:36:34

更新2:嗯,你在评论中说你还没有定义非泛型CacheCollection类型;但你接着说你有一个 Dictionary。这些陈述不可能同时成立,因此我猜测 CacheCollection 您的意思是 CacheCollection

现在的问题是:如果 X 类型为 X 无法转换为 X ="http://msdn.microsoft.com/en-us/library/dd799517.aspx" rel="nofollow noreferrer">不是协变的。也就是说,在您的情况下,仅仅因为 T 派生自 EntityBase 并不意味着 CacheCollection 派生自 CacheCollection ;

要具体说明其原因,请考虑 List 类型。假设您有一个 List 和一个 Liststring 派生自 object,但并不意味着 List 派生自 List;如果确实如此,那么您可以使用如下代码:

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 将从该基类派生。更好的是,使用简单的非通用界面。

interface ICacheCollection
{
    EntityBase Item(int id);
}

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

然后,对于您的字典,将其定义为 Dictionary,而不是 Dictionary>,其余代码应该一起来吧。


更新:看来您对我们隐瞒了!因此,您有一个非泛型 CacheCollection 基类,CacheCollection 派生自该基类,对吗?

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

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

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 中的适当参数生成 T code>CacheCollection 构造函数。

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

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> 作为您的后备存储,而不是 List> 因为它将使您的项目查找 O(1) 而不是 O( N)。

(我还建议使用私有成员来保存集合本身来实现此类,而不是直接从集合继承,因为使用继承会公开您可能想要隐藏的功能,例如 AddInsert< /代码>等)

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 a Dictionary<Type, CacheCollection>. These statements cannot both be true, so I am guessing that by CacheCollection you mean CacheCollection<EntityBase>.

Now here's the problem: a X<Derived> cannot be cast to a X<Base> if the X<T> type is not covariant. That is, in your case, just because T derives from EntityBase does not mean that CacheCollection<T> derives from CacheCollection<EntityBase>.

For a concrete illustration of why this is, consider the List<T> type. Say you have a List<string> and a List<object>. string derives from object, but it does not follow that List<string> derives from List<object>; if it did, then you could have code like this:

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

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.

interface ICacheCollection
{
    EntityBase Item(int id);
}

(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 a Dictionary<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 which CacheCollection<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 many CacheCollection<T> instances without sacrificing type safety.

Something like this (note: code modified based on new update above):

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

This would allow you to write code like the following:

var cache = new GeneralCache();

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

Well, if T derives from EntityBase but does not have a parameterless constructor, your best bet is going to be to specify a factory method that will generate a T for the appropriate parameters in your CacheCollection<T> constructor.

Like this (note: code modified based on new update above):

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

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 a List<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.)

心安伴我暖 2024-09-22 19:36:34

目前,没有什么可以阻止您(或其他人)声明 CacheCollection 实例,其中 T 是无法直接创建的内容(如果 T<例如,/code> 代表一个抽象类)。正如错误提示,您可以通过将 new() 约束添加到通用参数来要求 T 是“可创建的”:

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

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

T entity = new T { Id = Id };

Presently there is nothing preventing you (or someone else) from declaring an instance of CacheCollection<T> where T is something that cannot be directly created (if T represented an abstract class, for example). As the error hints, you can require that T be 'creatable' by adding the new() constraint to the generic parameter:

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

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 type int. Probably the easiest way around that issue is to create an instance of T and then assign its Id property (assuming that EntityBase defines such a property):

T entity = new T { Id = Id };
话少心凉 2024-09-22 19:36:34

我相信您收到的编译器错误会告诉您解决方案。您必须添加另一个类型约束,以便它知道您可以创建实例。您需要做的就是添加 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 });

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

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()

public class CacheCollection<T> : List<CacheItem<T>> where T : EntityBase, 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:

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

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:

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

But again this can't be guaranteed at runtime unless you keep up with all your child class implementations.

夏天碎花小短裙 2024-09-22 19:36:34

两件事:

// 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;
...

Two things:

// 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;
...
九八野马 2024-09-22 19:36:34

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

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

通过使用分部类可以轻松地将接口声明添加到实体类中:

public partial MyClass : IId { }

您的类定义现在变为:

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

What I did in similar situations is create an IId interface

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

It is easy to add the interface declaration to your entity classes through the use of partial classes:

public partial MyClass : IId { }

Your class definition now becomes:

public class CacheCollection<T> : List<CacheItem<T>> where T : EntityBase, IId, new()
有木有妳兜一样 2024-09-22 19:36:34
T entity = (T)Activator.CreateInstance(typeof(T), Id);
T entity = (T)Activator.CreateInstance(typeof(T), Id);
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文