Reputation: 22285
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
Reputation: 12934
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;
}
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;
}
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
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
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
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
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