Передача аргументов в С# generic new() шаблонного типа

Я пытаюсь создать новый объект типа T через его конструктор при добавлении в список.

Я получаю ошибку компиляции: Сообщение об ошибке:

'T': невозможно предоставить аргументы при создании экземпляра переменной

Но у моих классов есть аргумент конструктора! Как я могу сделать эту работу?

public static string GetAllItems<T>(...) where T : new()
{
   ...
   List<T> tabListItems = new List<T>();
   foreach (ListItem listItem in listCollection) 
   {
       tabListItems.Add(new T(listItem)); // error here.
   } 
   ...
}

Ответы

Ответ 1

Чтобы создать экземпляр родового типа в функции, вы должны ограничить его "новым" флагом.

public static string GetAllItems<T>(...) where T : new()

Однако это будет работать только тогда, когда вы хотите вызвать конструктор, который не имеет параметров. Не здесь. Вместо этого вам придется предоставить еще один параметр, который позволяет создавать объект на основе параметров. Самый простой - это функция.

public static string GetAllItems<T>(..., Func<ListItem,T> del) {
  ...
  List<T> tabListItems = new List<T>();
  foreach (ListItem listItem in listCollection) 
  {
    tabListItems.Add(del(listItem));
  }
  ...
}

Затем вы можете вызвать его так

GetAllItems<Foo>(..., l => new Foo(l));

Ответ 2

в .Net 3.5 и после использования класса активатора:

(T)Activator.CreateInstance(typeof(T), args)

Ответ 3

Поскольку никто не потрудился опубликовать ответ "Отражение" (который я лично считаю лучшим ответом), здесь говорится:

public static string GetAllItems<T>(...) where T : new()
{
   ...
   List<T> tabListItems = new List<T>();
   foreach (ListItem listItem in listCollection) 
   {
       Type classType = typeof(T);
       ConstructorInfo classConstructor = classType.GetConstructor(new Type[] { listItem.GetType() });
       T classInstance = (T)classConstructor.Invoke(new object[] { listItem });

       tabListItems.Add(classInstance);
   } 
   ...
}

Изменить: этот ответ устарел из-за .NET 3.5 Activator.CreateInstance, однако он по-прежнему полезен в более старых версиях .NET.

Ответ 4

Инициализатор объекта

Если ваш конструктор с параметром ничего не делает, кроме установки свойства, вы можете сделать это в С# 3 или лучше, используя инициализатор объекта вместо вызова конструктора (что невозможно, как уже упоминалось) :

public static string GetAllItems<T>(...) where T : new()
{
   ...
   List<T> tabListItems = new List<T>();
   foreach (ListItem listItem in listCollection) 
   {
       tabListItems.Add(new T() { YourPropertyName = listItem } ); // Now using object initializer
   } 
   ...
}

Используя это, вы всегда можете поместить любую логику конструктора в конструктор по умолчанию (пустой).

Activator.CreateInstance()

В качестве альтернативы вы можете вызвать Activator.CreateInstance() следующим образом:

public static string GetAllItems<T>(...) where T : new()
{
   ...
   List<T> tabListItems = new List<T>();
   foreach (ListItem listItem in listCollection) 
   {
        object[] args = new object[] { listItem };
        tabListItems.Add((T)Activator.CreateInstance(typeof(T), args)); // Now using Activator.CreateInstance
   } 
   ...
}

Обратите внимание, что Activator.CreateInstance может иметь некоторые потери производительности, которых вы можете избежать, если скорость выполнения является главным приоритетом, а другой вариант доступен для вас.

Ответ 5

Это не будет работать в вашей ситуации. Вы можете указать только ограничение, что у него есть пустой конструктор:

public static string GetAllItems<T>(...) where T: new()

Что вы можете сделать, так это использовать инъекцию свойств, определяя этот интерфейс:

public interface ITakesAListItem
{
   ListItem Item { set; }
}

Затем вы можете изменить свой метод следующим образом:

public static string GetAllItems<T>(...) where T : ITakesAListItem, new()
{
   ...
   List<T> tabListItems = new List<T>();
   foreach (ListItem listItem in listCollection) 
   {
       tabListItems.Add(new T() { Item = listItem });
   } 
   ...
}

Другой альтернативой является метод Func, описанный JaredPar.

Ответ 6

Очень старый вопрос, но новый ответ; -)

Версия ExpressionTree: (Я думаю, что это самые быстрые и быстрые решения)

Как сказал Велли Тамбунан, "мы могли бы также использовать дерево выражений для построения объекта"

Это создаст "конструктор" (функцию) для заданного типа/параметров. Он возвращает делегат и принимает типы параметров как массив объектов.

Вот он:

// this delegate is just, so you don't have to pass an object array. _(params)_
public delegate object ConstructorDelegate(params object[] args);

public static ConstructorDelegate CreateConstructor(Type type, params Type[] parameters)
{
    // Get the constructor info for these parameters
    var constructorInfo = type.GetConstructor(parameters);

    // define a object[] parameter
    var paramExpr = Expression.Parameter(typeof(Object[]));

    // To feed the constructor with the right parameters, we need to generate an array 
    // of parameters that will be read from the initialize object array argument.
    var constructorParameters = parameters.Select((paramType, index) =>
        // convert the object[index] to the right constructor parameter type.
        Expression.Convert(
            // read a value from the object[index]
            Expression.ArrayAccess(
                paramExpr,
                Expression.Constant(index)),
            paramType)).ToArray();

    // just call the constructor.
    var body = Expression.New(constructorInfo, constructorParameters);

    var constructor = Expression.Lambda<ConstructorDelegate>(body, paramExpr);
    return constructor.Compile();
}

Пример MyClass:

public class MyClass
{
    public int TestInt { get; private set; }
    public string TestString { get; private set; }

    public MyClass(int testInt, string testString)
    {
        TestInt = testInt;
        TestString = testString;
    }
}

Применение:

// you should cache this 'constructor'
var myConstructor = CreateConstructor(typeof(MyClass), typeof(int), typeof(string));

// Call the `myConstructor` fucntion to create a new instance.
var myObject = myConstructor(10, "test message");

введите описание изображения здесь


Другой пример: передача типов в виде массива

var type = typeof(MyClass);
var args = new Type[] { typeof(int), typeof(string) };

// you should cache this 'constructor'
var myConstructor = CreateConstructor(type, args);

// Call the `myConstructor` fucntion to create a new instance.
var myObject = myConstructor(10, "test message");

DebugView выражения

.Lambda #Lambda1<TestExpressionConstructor.MainWindow+ConstructorDelegate>(System.Object[] $var1) {
    .New TestExpressionConstructor.MainWindow+MyClass(
        (System.Int32)$var1[0],
        (System.String)$var1[1])
}

Это эквивалентно генерируемому коду:

public object myConstructor(object[] var1)
{
    return new MyClass(
        (System.Int32)var1[0],
        (System.String)var1[1]);
}

Малый недостаток

Все значения параметров параметров помещаются в поле, когда они передаются как массив объектов.


Простой тест производительности:

private void TestActivator()
{
    Stopwatch sw = Stopwatch.StartNew();
    for (int i = 0; i < 1024 * 1024 * 10; i++)
    {
        var myObject = Activator.CreateInstance(typeof(MyClass), 10, "test message");
    }
    sw.Stop();
    Trace.WriteLine("Activator: " + sw.Elapsed);
}

private void TestReflection()
{
    var constructorInfo = typeof(MyClass).GetConstructor(new[] { typeof(int), typeof(string) });

    Stopwatch sw = Stopwatch.StartNew();
    for (int i = 0; i < 1024 * 1024 * 10; i++)
    {
        var myObject = constructorInfo.Invoke(new object[] { 10, "test message" });
    }

    sw.Stop();
    Trace.WriteLine("Reflection: " + sw.Elapsed);
}

private void TestExpression()
{
    var myConstructor = CreateConstructor(typeof(MyClass), typeof(int), typeof(string));

    Stopwatch sw = Stopwatch.StartNew();

    for (int i = 0; i < 1024 * 1024 * 10; i++)
    {
        var myObject = myConstructor(10, "test message");
    }

    sw.Stop();
    Trace.WriteLine("Expression: " + sw.Elapsed);
}

TestActivator();
TestReflection();
TestExpression();

Результаты:

Activator: 00:00:13.8210732
Reflection: 00:00:05.2986945
Expression: 00:00:00.6681696

Использование Expressions равно +/- в 8 раз быстрее, чем использование ConstructorInfo и +/- в 20 раз быстрее, чем использование Activator

Ответ 7

Вам нужно добавить, где T: new(), чтобы компилятор знал, что T гарантированно предоставит конструктор по умолчанию.

public static string GetAllItems<T>(...) where T: new()

Ответ 8

Если вы просто хотите инициализировать поле или свойство элемента с помощью параметра конструктора, в С#> = 3 вы можете сделать это очень просто:

public static string GetAllItems<T>(...) where T : InterfaceOrBaseClass, new() 
{ 
   ... 
   List<T> tabListItems = new List<T>(); 
   foreach (ListItem listItem in listCollection)  
   { 
       tabListItems.Add(new T{ BaseMemberItem = listItem }); // No error, BaseMemberItem owns to InterfaceOrBaseClass. 
   }  
   ... 
} 

Это то же самое, что сказал Гарри Шатлер, но я бы хотел добавить дополнительную записку.

Конечно, вы можете использовать трюк со свойством, чтобы сделать больше, чем просто установить значение поля. Свойство "set()" может запускать любую обработку, необходимую для настройки связанных полей, и любую другую потребность в самом объекте, в том числе проверку, чтобы увидеть, должна ли произойти полная инициализация перед использованием объекта, имитируя полное конструирование ( да, это уродливый обходной путь, но он преодолевает ограничение M $ new()).

Я не могу быть уверен, что это запланированная дыра или случайный побочный эффект, но это работает.

Очень забавно, как люди из MS добавляют новые функции в язык и, похоже, не проводят полный анализ побочных эффектов. Все общее является хорошим доказательством этого...

Ответ 9

Я обнаружил, что получаю сообщение об ошибке "не могу предоставить аргументы при создании экземпляра параметра типа T", поэтому мне нужно было сделать это:

var x = Activator.CreateInstance(typeof(T), args) as T;

Ответ 10

Если у вас есть доступ к классу, который вы собираетесь использовать, вы можете использовать этот подход, который я использовал.

Создайте интерфейс, у которого есть альтернативный создатель:

public interface ICreatable1Param
{
    void PopulateInstance(object Param);
}

Создайте свои классы с пустым создателем и реализуйте этот метод:

public class MyClass : ICreatable1Param
{
    public MyClass() { //do something or nothing }
    public void PopulateInstance (object Param)
    {
        //populate the class here
    }
}

Теперь используйте ваши общие методы:

public void MyMethod<T>(...) where T : ICreatable1Param, new()
{
    //do stuff
    T newT = new T();
    T.PopulateInstance(Param);
}

Если у вас нет доступа, оберните целевой класс:

public class MyClass : ICreatable1Param
{
    public WrappedClass WrappedInstance {get; private set; }
    public MyClass() { //do something or nothing }
    public void PopulateInstance (object Param)
    {
        WrappedInstance = new WrappedClass(Param);
    }
}

Ответ 11

Это своего рода mucky, и когда я говорю, что это mucky, я могу означать отвращение, но предположим, что вы можете представить свой параметризованный тип пустым конструктором, а затем:

public static T GetTInstance<T>() where T: new()
{
    var constructorTypeSignature = new Type[] {typeof (object)};
    var constructorParameters = new object[] {"Create a T"};
    return (T) new T().GetType().GetConstructor(constructorTypeSignature).Invoke(constructorParameters);
}

Эффективно позволит вам построить объект из параметризованного типа с аргументом. В этом случае я предполагаю, что конструктор, который я хочу, имеет один аргумент типа object. Мы создаем фиктивный экземпляр T, используя разрешенный пустой конструктор, а затем используем отражение, чтобы получить один из его других конструкторов.

Ответ 12

Я иногда использую подход, похожий на ответы, используя инъекцию свойств, но сохраняет код чище. Вместо того, чтобы иметь базовый класс/интерфейс с набором свойств, он содержит только (виртуальный) метод Initialize(), который действует как "конструктор бедных людей". Затем вы можете позволить каждому классу обрабатывать свою собственную инициализацию так же, как и конструктор, что также добавляет удобный способ обработки цепей наследования.

Если вы часто находите себя в ситуациях, когда я хочу, чтобы каждый класс в цепочке инициализировал свои уникальные свойства, а затем вызывается его родительский метод Initialize(), который, в свою очередь, инициализирует родительские уникальные свойства и т.д. Это особенно полезно при использовании разных классов, но с аналогичной иерархией, например бизнес-объектов, которые отображаются в/из DTO: s.

Пример, который использует общий словарь для инициализации:

void Main()
{
    var values = new Dictionary<string, int> { { "BaseValue", 1 }, { "DerivedValue", 2 } };

    Console.WriteLine(CreateObject<Base>(values).ToString());

    Console.WriteLine(CreateObject<Derived>(values).ToString());
}

public T CreateObject<T>(IDictionary<string, int> values)
    where T : Base, new()
{
    var obj = new T();
    obj.Initialize(values);
    return obj;
}

public class Base
{
    public int BaseValue { get; set; }

    public virtual void Initialize(IDictionary<string, int> values)
    {
        BaseValue = values["BaseValue"];
    }

    public override string ToString()
    {
        return "BaseValue = " + BaseValue;
    }
}

public class Derived : Base
{
    public int DerivedValue { get; set; }

    public override void Initialize(IDictionary<string, int> values)
    {
        base.Initialize(values);
        DerivedValue = values["DerivedValue"];
    }

    public override string ToString()
    {       
        return base.ToString() + ", DerivedValue = " + DerivedValue;
    }
}

Ответ 13

Если вам нужно только преобразование из ListItem в тип T, вы можете реализовать это преобразование в классе T в качестве оператора преобразования.

public class T
{
    public static implicit operator T(ListItem listItem) => /* ... */;
}

public static string GetAllItems(...)
{
    ...
    List<T> tabListItems = new List<T>();
    foreach (ListItem listItem in listCollection) 
    {
        tabListItems.Add(listItem);
    } 
    ...
}

Ответ 14

Я считаю, что вам нужно ограничить T с помощью инструкции where, чтобы разрешать объекты с новым конструктором.

Теперь он принимает что угодно, включая объекты без него.