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

389

Я пытаюсь создать новый объект типа 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 комментарий
Теги:
generics
new-operator

13 ответов

378
Лучший ответ

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

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));
  • 0
    Как это будет работать при вызове из общего класса? Я разместил свой код в ответе ниже. Я не знаю конкретный класс внутри, так как это общий класс. Есть ли способ обойти это? Я не хочу использовать другое предложение использования синтаксиса инициализатора свойства, поскольку это обойдёт логику, которую я использую в конструкторе
  • 0
    добавил мой код в другой вопрос stackoverflow.com/questions/1682310/…
Показать ещё 2 комментария
287

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

(T)Activator.CreateInstance(typeof(T), args)
Показать ещё 7 комментариев
48

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

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.

  • 6
    Разве рефлексия не несет существенного снижения производительности?
  • 13
    «Значительный» будет зависеть от приложения. В большинстве случаев, вероятно, нет.
Показать ещё 6 комментариев
26

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

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

  • 0
    это предотвращает T от защиты его инварианты (при условии , что T имеет> 0 зависимостей или требуемые значения, теперь вы можете создавать экземпляры T , которые находятся в недопустимом / нерабочем состоянии. если T не является чем - то мертвый просто как DTO оч ViewModel, я бы скажи избегай этого.
17

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

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.

  • 0
    это могло бы обойти любую логику в конструкторе, который принимает аргументы, правда? Я хотел бы сделать что-то вроде подхода Джареда, но я вызываю метод внутри класса, поэтому не знаю, что это за конкретный тип ... хммм
  • 3
    Правильно, это вызывает логику конструктора по умолчанию T (), а затем просто устанавливает свойство "Item". Если вы пытаетесь вызвать логику конструктора не по умолчанию, это вам не поможет.
13

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

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

Изображение 1107


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

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

  • 0
    Есть ли у вас понимание того, что делать, если вы хотите создать MyClass <T> с помощью конструктора public MyClass (T data). В этом случае Expression.Convert генерирует исключение, и если я использую базовый класс универсального ограничения для преобразования в, тогда Expression.New выбрасывает, потому что информация конструктора предназначена для универсального типа.
7

Если вы просто хотите инициализировать поле или свойство элемента с помощью параметра конструктора, в С#> = 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 добавляют новые функции в язык и, похоже, не проводят полный анализ побочных эффектов. Все общее является хорошим доказательством этого...

  • 1
    Оба ограничения необходимы. InterfaceOrBaseClass информирует компилятор о поле / свойстве BaseMemberItem. Если закомментировано ограничение «new ()», это вызовет ошибку: Ошибка 6 Невозможно создать экземпляр типа переменной «T», поскольку у него нет ограничения new ()
  • 0
    Ситуация, с которой я столкнулся, была не совсем похожа на вопрос, который здесь задают, однако, этот ответ привел меня туда, куда мне нужно было идти, и, похоже, он работает очень хорошо.
Показать ещё 1 комментарий
7

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

public static string GetAllItems<T>(...) where T: new()
  • 1
    ОБНОВЛЕНИЕ: правильное сообщение об ошибке: «T»: не может предоставить аргументы при создании экземпляра переменной
  • 0
    Это потому, что вы не используете пустой конструктор, вы передаете ему аргумент объекта. Нет способа справиться с этим, не указав, что универсальный тип имеет новый (объектный) параметр.
Показать ещё 4 комментария
6

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

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

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

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

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

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

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

-3

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

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

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню