6

I'm trying to figure out how to use templates in C#. I wrote this:

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

But I get an error:

error CS0304: Cannot create an instance of the variable type 'TValue' because it does not have the new() constraint

c00000fd
  • 20,994
  • 29
  • 177
  • 400
  • 4
    First - in c# those constructs are called generics. They are somewhat different then templates known from C++ and it's worth to remember this. – Jarek May 24 '13 at 08:52

5 Answers5

8

#1: New constraint with interface

Add a contraint to TValue telling the compiler it has a parameterless constructor. You can do this by adding the keyword new to the contraint of TValue. This way you can at least construct an item.

You cannot use paramters of generic parameter types. But you can use another contraint to define some properties:

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: Using ICloneable

There is a second solution, and it is a bit the same. Make the value responsible for cloning it self, using 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: Using Activator

But since you want to create a deep clone, there is another way, using the Activator. This method is NOT type safe and can produce runtime exceptions when the type does not support that constructor call:

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

Above method can also be replace by using reflection and get the correct ConstructorInfo and use this to create new items. It is the same thing what Activator does and has the same risk.

BTW: In C# it is called 'generic', not 'template' as in C++.

Martin Mulder
  • 12,642
  • 3
  • 25
  • 54
  • Thanks for the explanation. Yes, of course, they had to rename it :) – c00000fd May 24 '13 at 09:21
  • 1
    Copying other peoples answers into your own and then down-voting all other answers should probably be considered poor etiquette. – Justin May 24 '13 at 09:21
  • @Justin: I agree. That would be poor etiquette. If you are refering to the `ICloneable`, you must believe me that I did NOT read your answer while or before I was writing my answer. When I finished my answer, I started reviewing the other answers. Furthermore I downvoted your answer NOT because you are using ICloneable. We just had the same idea at the same time. My reasons for downvoting are explained. – Martin Mulder May 24 '13 at 09:26
2

The problem here is that you are calling a constructor with a parameter, thus the new constraint is not sufficient. What you can do is to dynamically invoke the constructor like this (you have then to ensure that your TValue class has a matching constructor):

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

Or with 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();
}

Usage example:

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


Otherwise, this link may also help you: Passing arguments to C# generic new() of templated type

Community
  • 1
  • 1
Bidou
  • 7,378
  • 9
  • 47
  • 70
  • -1: The OP did not ask how to convert his loop to LINQ. It is confusing and LINQ is NOT required for the answer. Please modify your answer to show a for loop with your ConstructorInfo solution. – Martin Mulder May 24 '13 at 09:11
  • @MartinMulder You are quite capable of editing the question yourself, this would have been far more useful than down-voting an otherwise perfectly acceptable answer – Justin May 24 '13 at 09:24
  • @Justin: That is correct. I can edit answers of others. But leaving comments helps people to learn from their mistakes. If I edit their answers, they learn nothing an might repeat their mistakes. – Martin Mulder May 24 '13 at 09:33
  • @Martin Mulder: I added an example without linq. Thanks for your comment – Bidou May 24 '13 at 09:42
  • @Bidou: Thanks! I removed the downvote. I also edited your answer to move the GetConstructor code out of the loop. – Martin Mulder May 24 '13 at 09:46
  • @MArtinMulder linq is not required neither is reflection however that doesn't meen that this answer does not solve the problem. The answer is helpfull in that respect. OP didn't specify that the example _couldn't_ be in linq – Rune FS May 24 '13 at 10:21
  • @Rune FS: The ConstructorInfo was part of the solution. LINQ was not. Why add new technologies or methodologies if they are not an addition to the answer? Since the developer is new to C#, perhaps he does not even understand LINQ. It is a great assumption that he does. Why complicate the answer to the OP if it is not required? – Martin Mulder May 24 '13 at 11:00
  • @MartinMulder Whether or not LINQ is a complication or not depends on the reader but the real answer (since we are talking about a dv reason) is was it helpful? Well if helpful means it can help to solve the problem then the answer is yes. – Rune FS May 24 '13 at 11:08
  • @Rune FS: If it was helpful is only for the OP to decide. For other people can only judge if it is a good or bad answer IN THEIR OPION. At my work I am a team leider and a teacher. I know that, giving answers to people, the answer should be as simple and as close to the orinal answer. This answers adds two new things: 1) Reflection (=part of the solution) and 2) LINQ (which is only noise). – Martin Mulder May 24 '13 at 11:49
  • @Rune FS: But I do not know what this discussion is about. Bidou adjust his answer after my comment. He split his answer up in two sections. One with the original for-loop and one with the LINQ. Now the OP can choose what he will use. Everybody happy, right? – Martin Mulder May 24 '13 at 11:50
2

If you want to use the new operator with generic arguments then you need to specify that the type argument must have a default constructor, like so

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

However this doesn't really help that much on its own as you don't have a copy of the original you just have a new object - unfortunately you can't specify that the type must support a specific constructor, so you can't call copy constructors or anything like that.

What I'd probably do is this

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

This means that you can only use this method with types that support ICloneable, however this is probably as good as you are going to get without reverting to using reflection and other tricks.

Justin
  • 84,773
  • 49
  • 224
  • 367
  • -1: Code sample #1 is not what he wants (he wants to copy one value to another). And your second example also has a nice mistake. :) – Martin Mulder May 24 '13 at 09:12
  • @MartinMulder Code sample #1 is explaining how to call `new` with generic type arguments (the title of the question), I explain after the sample why this won't help and then go on to suggest an alternative method that will help. Neither sample will compile because they don't return a value - they are short samples to illustrate a technique. If there is some other mistake then it would be helpful to point it out (or better still correct it) so that others can benefit from it. – Justin May 24 '13 at 09:16
  • To quote yourself in your own answer "this doesn't really help that much". True. If you had elaborated some more and got a total answer with the `new`, than it was a nice addition. But you abandonned your own course and wnt another way. YHour second example (I know it does not compile, that was not my point). Once it DOES compile, your code is cloning the wrong objects. – Martin Mulder May 24 '13 at 09:20
0

Your method signature should look like this:

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

However, you will probably need to also change the object initialization to have en empty constructor and then set any properties afterwards:

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

See also the official documentation on the new constraint.

dlebech
  • 1,817
  • 14
  • 27
-1

The compiler does not know that the generic type can be instantiated with new unless you explicitly specify that with a where constraint.

Marius Bancila
  • 16,053
  • 9
  • 49
  • 91
  • Well, it's a template. I technically don't know it either :) – c00000fd May 24 '13 at 08:49
  • -1: I do not feel the OP is helped with this answer. You are repeating something the OP already knows: The compiles does not know something and therefor gives an error. The implicit question is, how to let the compiler know... – Martin Mulder May 24 '13 at 09:13
  • @MartinMulder which is to provide a constraint as noted in this answer. However the particular constraint needed is not supported. You can only specify the requirement of a default constructor – Rune FS May 24 '13 at 09:18
  • 1
    @Rune FS: True. But since the OP is using the term 'Template', he clearly is not using C# and generics for a long time. So he might not even know about the concept of contraints. Telling him to use contraints without an example code, is the same thing as telling him to use blablabla. – Martin Mulder May 24 '13 at 09:22
  • @MartinMulder you said "You are repeating something the OP already knows" and "now you say "he might not even know about the concept of constraints" I agree with the latter, which was the point of my comment. He might not know of constraints so the answer does provide some information. could it still be improve e.g. with an example, of course it could – Rune FS May 24 '13 at 11:03