Передача аргументов в универсальный метод new() шаблонного типа C#.

StackOverflow https://stackoverflow.com/questions/840261

Вопрос

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

Решение

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

Изменить. Этот ответ устарел из-за Activator.CreateInstance .NET 3.5, однако он все еще полезен в более старых версиях .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
   } 
   ...
}

Обратите внимание, что 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: (Я думаю, самое быстрое и чистое решение)

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

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

Вот:

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

Отладочное представление выражения

.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

Вам нужно добавить где 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. 
   }  
   ... 
} 

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

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

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

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

Я обнаружил, что получаю сообщение об ошибке &; не могу предоставить аргументы при создании экземпляра параметра типа 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);
    }
}

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top