【问题标题】:Dilemma in calling constructor of generic class调用泛型类的构造函数的困境
【发布时间】:2013-02-21 13:05:12
【问题描述】:

我有一个看起来像这样的通用单例:

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 参数,并且没有其他方法可以这样做。任何有关如何解决此问题的想法都将受到高度赞赏。

【问题讨论】:

标签: c# constructor initialization generics


【解决方案1】:

通常,您会将类型 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&lt;T&gt;上有一个构造函数new List&lt;string&gt;(int capacity),可以这样调用:

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

当然,之后您可能想进行一些转换(到T)。

【讨论】:

  • 这听起来是个好主意,但我不能说 value.ID = id,它只是不会让我,虽然我已经很接近了。还有其他提示吗?顺便说一句,非常感谢:)
  • @AlexOltean 我的回答的第二部分是一个alternative,因为你不能说value.ID = id。试试看:D
  • @AlexOltean 虽然这个答案可能有效,但它实际上根本不是正确的做法。反射在这里是完全没有根据的。除了速度较慢之外,这不是正确的方法。您的代码更能表达它在没有反射的情况下所做的事情。通过反射,您使事情变得模糊不清。随着时间的推移,你会明白这一点。
  • @nawfal 我不赞成反思。我的第一个建议与您的基本相同,但您在课堂上和界面上展示了它。我从字面上解释了 “我必须传递那个 id 参数,并且没有其他方法可以这样做”,然后唯一的解决方案就是反射,你必须同意。
【解决方案2】:

为此,您应该指定T 是什么。你的Cache&lt;T&gt; 能装什么? 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&lt;T&gt;Ts)。

【讨论】:

  • 这也行,特别是因为我会避免使用反射
  • @AlexOltean 很高兴它成功了。我在没有看到您的评论的情况下评论了其他帖子。干杯
  • @AlexOltean 我编辑了我的答案,您可以根据我的建议使您的代码更快。只是现在它每次都会更新与 id 对应的值(如果存在 id 键)并一次性返回它。这取决于你的LoadFromSharePoint 有多贵,我认为一点也不贵。
【解决方案3】:

为了在没有任何约束的情况下使用泛型类型的构造函数,并且在类中,需要使用语法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

【讨论】:

    猜你喜欢
    • 2017-03-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-17
    • 2019-09-19
    • 2010-10-16
    • 2019-02-20
    • 1970-01-01
    相关资源
    最近更新 更多