Как использовать новый оператор с шаблоном в 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.

Другие вопросы по тегам