c00000fd
c00000fd

Reputation: 22285

How to use new operator with a template in C#

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

Upvotes: 6

Views: 3367

Answers (5)

Martin Mulder
Martin Mulder

Reputation: 12934

#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++.

Upvotes: 8

nicolas
nicolas

Reputation: 7668

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

Upvotes: 2

Justin
Justin

Reputation: 86779

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.

Upvotes: 2

dlebech
dlebech

Reputation: 1839

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.

Upvotes: 0

Marius Bancila
Marius Bancila

Reputation: 16338

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

Upvotes: -1

Related Questions