Передача аргументов в C# 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.
   } 
   ...
}

13 ответов


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

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));

в .Net 3.5 и после того, как вы можете использовать класс активатора:

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

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

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);
   } 
   ...
}

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


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

Если ваш конструктор с параметром не делает ничего, кроме установки свойства, вы можете сделать это в C# 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
   } 
   ...
}

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

активатор.CreateInstance ()

кроме того, вы можете позвонить активатор.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
   } 
   ...
}

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


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

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.


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

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

как Модель Welly Тамбунан сказал: "мы также можем использовать дерево выражений для создания объекта"

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

вот это:

// 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");

enter image description here


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

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]);
}

недостаток

все параметры valuetypes упаковываются, когда они передаются как массив объектов.


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

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 is +/ -в 8 раз быстрее использования ConstructorInfo и +/- в 20 раз быстрее использования Activator


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

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

Если вы просто хотите инициализировать член поле или свойство с параметром конструктора в C# >= 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. 
   }  
   ... 
} 

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

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

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

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

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);
    }
}

Я иногда использую подход, который напоминает ответы с помощью инъекции свойств, но сохраняет код чище. Вместо того, чтобы иметь базовый класс/интерфейс с набором свойств, он содержит только (виртуальный) 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;
    }
}

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

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, используя ограничение, разрешенное пустым конструктором, а затем используем отражение, чтобы получить его из других конструкторов.


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

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