【问题标题】:How to use new operator with a template in C#如何在 C# 中将 new 运算符与模板一起使用
【发布时间】:2013-05-24 08:44:36
【问题描述】:

我正在尝试弄清楚如何在 C# 中使用模板。我是这样写的:

public static List<TValue> deepCopyList<TValue>(List<TValue> src)
{
    List<TValue> arr = new List<TValue>();

    for (int i = 0; i < src.Count; i++)
    {
        arr.Add(new TValue(src[i]));   //Error on this line
    }

    return arr;
}

但我得到一个错误:

错误 CS0304:无法创建变量类型“TValue”的实例 因为它没有 new() 约束

【问题讨论】:

  • 首先 - 在 c# 中,这些构造称为泛型。它们与 C++ 中已知的模板有些不同,值得记住这一点。

标签: c# .net list templates


【解决方案1】:

#1:带有接口的新约束

TValue 添加一个约束,告诉编译器它有一个无参数的构造函数。您可以通过将关键字new 添加到TValue 的约束来做到这一点。这样你至少可以构建一个项目。

您不能使用泛型参数类型的参数。但是你可以使用另一个约束来定义一些属性:

public interface IMyValue<TValue>
{
    void CopyFrom(TValue original);
}

public static List<TValue> deepCopyList<TValue>(List<TValue> src)
    where TValue: IMyValue<TValue>, new() // <== Setting the constraints of TValue.
{
    List<TValue> arr = new List<TValue>();

    for (int i = 0; i < src.Count; i++)
    {
        TValue value = new TValue();
        value.CopyFrom(src[i]);
        arr.Add(value); // No error.
    }

    return arr;
}

#2:使用 ICloneable

还有第二种解决方案,有点相同。使用 ICloneable 使值负责关闭它自己:

public static List<TValue> deepCopyList<TValue>(List<TValue> src)
    where TValue: ICloneable // <== Setting the constraints of TValue.
{
    List<TValue> arr = new List<TValue>();

    for (int i = 0; i < src.Count; i++)
    {
        TValue value = (TValue)src[i].Clone();
        arr.Add(value); // No error.
    }

    return arr;
}

#3:使用激活器

但既然你想创建一个深度克隆,还有另一种方法,使用Activator。此方法不是类型安全的,并且当类型不支持该构造函数调用时会产生运行时异常:

public static List<TValue> deepCopyList<TValue>(List<TValue> src)
{
    List<TValue> arr = new List<TValue>();

    for (int i = 0; i < src.Count; i++)
    {
        TValue value = (TValue)Activator.CreateInstance(typeof(TValue), src[i]);
        arr.Add(value);  // Possible runtime rror.
    }

    return arr;
}

上述方法也可以通过使用反射来替换并获得正确的ConstructorInfo 并使用它来创建新项目。 Activator 所做的事情和风险相同。

顺便说一句:在 C# 中,它被称为“通用”,而不是 C++ 中的“模板”。

【讨论】:

  • 感谢您的解释。是的,当然,他们不得不重命名它:)
  • 将其他人的答案复制到您自己的答案中,然后对所有其他答案投反对票可能会被视为不礼貌。
  • @Justin:我同意。那将是糟糕的礼仪。如果您指的是ICloneable,您必须相信我在写答案时或之前没有阅读您的答案。当我完成我的答案时,我开始查看其他答案。此外,我不赞成您的回答,不是因为您使用的是 ICloneable。我们只是在同一时间有同样的想法。解释了我拒绝投票的原因。
【解决方案2】:

这里的问题是你正在调用一个带参数的构造函数,因此new 约束是不够的。您可以做的是像这样动态调用构造函数(然后您必须确保您的 TValue 类具有匹配的构造函数):

public static List<TValue> DeepCopyList<TValue>(List<TValue> values)
{
    List<TValue> list = new List<TValue>();
    var ctor = typeof(TValue).GetConstructor(new[] {typeof(TValue)});
    foreach (TValue value in values)
    {
        list.Add((TValue)ctor.Invoke(new object[] {value}));
    }
    return list; 
}

或者使用 linq:

public static List<TValue> DeepCopyList<TValue>(List<TValue> values)
{
    return (from value in values 
      let ctor = typeof(TValue).GetConstructor(new[] {typeof(TValue)}) 
      select (TValue)ctor.Invoke(new object[] {value})).ToList();
}

使用示例:

public class Test
{
    public Test(Test test)
    {
        // Do what you want...
    }
}

List<Test> tests = new List<Test>() { new Test(null) };
List<Test> results = DeepCopyList(tests);  


否则,此链接也可能对您有所帮助:Passing arguments to C# generic new() of templated type

【讨论】:

  • -1:OP 没有询问如何将他的循环转换为 LINQ。这令人困惑,答案不需要 LINQ。请修改您的答案以使用您的 ConstructorInfo 解决方案显示 for 循环。
  • @MartinMulder 你完全有能力自己编辑问题,这比否决一个完全可以接受的答案要有用得多
  • @Justin:没错。我可以编辑其他人的答案。但离开 cmets 有助于人们从错误中吸取教训。如果我编辑他们的答案,他们将一无所获,可能会重蹈覆辙。
  • @Martin Mulder:我添加了一个没有 linq 的示例。感谢您的评论
  • @Bidou:谢谢!我删除了反对票。我还编辑了您的答案以将 GetConstructor 代码移出循环。
【解决方案3】:

如果您想将new 运算符与泛型参数一起使用,那么您需要指定类型参数必须具有默认构造函数,就像这样

public static List<T> deepCopyList<T>(List<T> src)
    where T : new()
{
    // I can now call new, like so
    var value = new T();
}

然而,这并没有真正帮助 ,因为您没有原件的副本,您只有一个新对象 - 不幸的是,您无法指定类型必须支持特定的构造函数,所以你不能调用复制构造函数或类似的东西。

我可能会这样做

public static List<T> deepCopyList<T>(List<T> src)
    where T : ICloneable
{
    var value = src[0].Clone();
}

这意味着您只能将此方法用于支持 ICloneable 的类型,但这可能与您在不使用反射和其他技巧的情况下获得的一样好。

【讨论】:

  • -1:代码示例 #1 不是他想要的(他想将一个值复制到另一个值)。你的第二个例子也有一个很好的错误。 :)
  • @MartinMulder 代码示例 #1 解释了如何使用泛型类型参数(问题的标题)调用 new,我在示例之后解释了为什么这无济于事,然后继续建议一种替代方法,这将有所帮助。这两个示例都不会编译,因为它们不返回值 - 它们是用于说明技术的简短示例。如果还有其他错误,那么指出(或更好地纠正它)会很有帮助,以便其他人可以从中受益。
  • 在你自己的回答中引用自己的话“这并没有那么大的帮助”。真的。如果您已经详细说明并通过new 得到了一个完整的答案,那么这是一个很好的补充。但是你放弃了你自己的课程并且想要另一种方式。 YHour 第二个例子(我知道它不能编译,那不是我的意思)。一旦编译完成,您的代码就会克隆错误的对象。
【解决方案4】:

您的方法签名应如下所示:

public static List<TValue> deepCopyList<TValue>(List<TValue> src) where TValue : new()

但是,您可能还需要将对象初始化更改为具有空的构造函数,然后设置任何属性:

TValue val = new TValue();
val.MyField = src[i]; // This only works if you further constrain the type of TValue
arr.Add(val);

另请参阅official documentation on the new constraint

【讨论】:

    【解决方案5】:

    编译器不知道泛型类型可以用 new 实例化,除非您使用 where 约束明确指定它。

    【讨论】:

    • 嗯,它是一个模板。从技术上讲,我也不知道 :)
    • -1:我觉得 OP 对这个答案没有帮助。您正在重复 OP 已经知道的内容:编译器不知道某些内容,因此会出现错误。隐含的问题是,如何让编译器知道...
    • @MartinMulder 提供此答案中所述的约束。但是,不支持所需的特定约束。您只能指定默认构造函数的要求
    • @Rune FS:是的。但由于 OP 使用的是“模板”一词,他显然很长时间没有使用 C# 和泛型。所以他可能甚至不知道约束的概念。告诉他在没有示例代码的情况下使用约束,与告诉他使用 blablabla 是一回事。
    • @MartinMulder 你说“你在重复 OP 已经知道的事情”和“现在你说“他甚至可能不知道约束的概念”我同意后者,这就是我的评论。他可能不知道约束,所以答案确实提供了一些信息。它是否仍然可以改进,例如举个例子,当然可以
    猜你喜欢
    • 1970-01-01
    • 2013-07-10
    • 2010-12-09
    • 2021-06-16
    • 1970-01-01
    • 1970-01-01
    • 2017-10-12
    相关资源
    最近更新 更多