Как использовать новый оператор с шаблоном в C#
Я пытаюсь выяснить, как использовать шаблоны в 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()
5 ответов
#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
делает и имеет такой же риск.
BTW: In C# it is called 'generic', not 'template' as in C++.
Если вы хотите использовать 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
Однако это, вероятно, так же хорошо, как вы собираетесь получить, не возвращаясь к использованию отражения и других уловок.
Проблема здесь в том, что вы вызываете конструктор с параметром, таким образом, 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);
В противном случае вам также может помочь эта ссылка: Передача аргументов в C# generic new() шаблонного типа
Ваша подпись метода должна выглядеть так:
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);
Смотрите также официальную документацию о новом ограничении.
Компилятор не знает, что универсальный тип может быть создан с помощью new, если вы явно не укажете это с ограничением where.