Передача аргументов в C # generic new () шаблонного типа - PullRequest
382 голосов
/ 08 мая 2009

Я пытаюсь создать новый объект типа 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 ]

392 голосов
/ 08 мая 2009

Чтобы создать экземпляр универсального типа в функции, вы должны ограничить его флагом "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));
312 голосов
/ 08 апреля 2011

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

(T)Activator.CreateInstance(typeof(T), args)
51 голосов
/ 16 июня 2010

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

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

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

28 голосов
/ 23 февраля 2012

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

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

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

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

17 голосов
/ 08 мая 2009

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

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.

15 голосов
/ 10 октября 2016

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

Версия 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");

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 Expression

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

7 голосов
/ 30 апреля 2010

Если вы просто хотите инициализировать поле или свойство элемента с помощью параметра конструктора, в 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. 
   }  
   ... 
} 

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

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

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

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

7 голосов
/ 08 мая 2009

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

public static string GetAllItems<T>(...) where T: new()
6 голосов
/ 06 ноября 2015

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

var x = Activator.CreateInstance(typeof(T), args) as T;
3 голосов
/ 13 июля 2017

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

Создать интерфейс с альтернативным создателем:

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