泛型类构造函数调用的困境

14
我有一个通用的单例模式,看起来像这样:
public class Cache<T>
{
    private Dictionary<Guid, T> cachedBlocks;

    // Constructors and stuff, to mention this is a singleton

    public T GetCache(Guid id)
    {
        if (!cachedBlocks.ContainsKey(id))
            cachedBlocks.Add(id, LoadFromSharePoint(id))
        return cachedBlocks[id];
    }

    public T LoadFromSharePoint(Guid id)
    {
        return new T(id)    // Here is the problem.
    }
}

错误信息为:

无法创建类型 T 的实例,因为它没有 new() 约束。

我必须提到,我必须传递该 id 参数,没有其他方法可以这样做。有什么解决方法吗?非常感谢。

一个相关的问题:https://dev59.com/pE_Ta4cB1Zd3GeqPFfpt?rq=1 - nawfal
3个回答

25
通常情况下,您需要将类型T限制为具有默认构造函数的类型,并调用该函数。然后,您需要添加一个方法或属性来提供id的值给实例。
public static T LoadFromSharePoint<T>(Guid id)
    where T : new()     // <-- Constrain to types with a default constructor
{
    T value = new T();
    value.ID = id;
    return value;
}

或者,既然你指定了必须通过构造函数提供id参数,那么你可以使用反射调用带参数的构造函数。 你必须确保类型定义了你想要调用的构造函数。 你不能将泛型类型T限制为具有特定构造函数(除了默认构造函数)的类型。 (例如,where T:new(Guid)不起作用。)

例如,我知道List<T>上有一个构造函数new List<string>(int capacity),可以像这样调用:

var type = typeof(List<String>);
object list = Activator.CreateInstance(type, /* capacity */ 20);

当然,您可能希望在此之后进行一些强制转换(到T)。

这听起来是个好主意,但我不能说value.ID = id,它就是不让我这么做,虽然我已经接近成功了。还有其他的建议吗?顺便说一下,非常感谢你到目前为止所做的一切 :) - Alex Oltean
@AlexOltean 我回答的第二部分是一个_替代方案_,当你不能说value.ID = id时可以尝试一下 :D - Daniel A.A. Pelsmaeker
@AlexOltean,虽然这个答案可能有效,但这绝不是正确的做法。在这里使用反射完全没有必要。除了速度较慢外,这也不是正确的方法。您的代码在没有反射的情况下更能表达其功能。使用反射会使事情变得模糊不清。您将会随着时间的推移而学习到这一点。 - nawfal
2
@nawfal 我不赞成使用反射。我的第一个建议与你的基本相同,但你在类和接口上展示了它。我字面理解了“我必须传递那个id参数,没有其他方法可以这样做”,那么唯一的解决方案就是反射,你也会同意这一点。 - Daniel A.A. Pelsmaeker

7
为了实现这一点,您应该指定 T 是什么。您的 Cache<T> 可以容纳任何类型吗?比如 TigerFridgeint?这不是一个良好的设计。您应该对其进行限制。您需要一个 T 的实例,该实例将使用 Guid 来构造实例。那不是一个通用的 T。而是一个非常具体的 T。请将您的代码更改为:
public class Cache<T> where T : Cacheable, new()
{
    private Dictionary<Guid, T> cachedBlocks;

    // Constructors and stuff, to mention this is a singleton

    public T GetCache(Guid id)
    {
        if (!cachedBlocks.ContainsKey(id))
            cachedBlocks.Add(id, LoadFromSharePoint(id))
        return cachedBlocks[id];

       //you're first checking for presence, and then adding to it
       //which does the same checking again, and then returns the
       //value of key again which will have to see for it again. 
       //Instead if its ok you can directly return

       //return cachedBlocks[id] = LoadFromSharePoint(id);

       //if your LoadFromSharePoint is not that expensive.
       //mind you this is little different from your original 
       //approach as to what it does.
    }

    public T LoadFromSharePoint(Guid id)
    {
        return new T { Key = id };    // Here is no more problem.
    }
}

public interface Cacheable
{
    Guid Key { get; set; }
}

现在,从接口Cacheable派生所有可缓存的内容(无论您将为其传递Cache<T>的任何T)。

那也可以,特别是因为我会避免使用反射。 - Alex Oltean
@AlexOltean 很高兴它起作用了。我在看到你的这条评论之前已经在另一篇帖子上发表了评论。干杯! - nawfal
@AlexOltean 我编辑了我的答案,你可以使用我的建议使你的代码更快。现在它每次更新与id对应的值(如果id键存在),并一次性返回它。这取决于你的 LoadFromSharePoint 有多昂贵,我认为根本不昂贵。 - nawfal

0
为了在类内部不带任何约束地使用泛型类型的构造函数,需要使用语法where T:class,new()
这使得可以在运行时根据目标类更改属性(字段)的值(而不仅仅是获取/设置属性)。
首先,声明泛型类:
public class Foo<T>   where T : class, new()
{
    public T oneEmptyElement()
    {
        return new T();
    }

    public T setAttribute(string attributeName, string attributeValue)
    {
        T objT = new T();
        System.Reflection.FieldInfo fld = typeof(T).GetField(attributeName);
        if (fld != null)
        {
            fld.SetValue(objT, attributeValue);
        }
        return objT;
    }

    public List<T> listOfTwoEmptyElements()
    {
        List<T> aList = new List<T>();
        aList.Add(new T());
        aList.Add(new T());
        return aList;
    }
}

先声明一个潜在的目标类:

public class Book
{
    public int name;
}

最后,可以这样调用:

        Foo<Book> fooObj = new Foo<Book>();

        Book aBook = fooObj.oneEmptyElement();
        aBook.name = "Emma";

        Book anotherBook = fooObj.setAttribute("name", "John");

        List<Book> aListOfBooks = fooObj.listOfTwoEmptyElements();
        aListOfBooks[0].name = "Mike";
        aListOfBooks[1].name = "Angelina";

        Console.WriteLine(aBook.name);    //Output Emma
        Console.WriteLine(anotherBook.name);    //Output John
        Console.WriteLine(aListOfBooks[0].name); // Output Mike
        Console.WriteLine(aListOfBooks[1].name);  // Output Angelina

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接