Как в C # создать экземпляр переданного универсального типа внутри метода?

99

Как я могу создать экземпляр типа T внутри моего InstantiateType<T>метода ниже?

Я получаю сообщение об ошибке: «T» - это «параметр типа», но используется как «переменная». :

(ПРОКРУТИТЕ ВНИЗ, ЧТОБЫ ОТВЕЧИТЬ ОТВЕТ)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace TestGeneric33
{
    class Program
    {
        static void Main(string[] args)
        {
            Container container = new Container();
            Console.WriteLine(container.InstantiateType<Customer>("Jim", "Smith"));
            Console.WriteLine(container.InstantiateType<Employee>("Joe", "Thompson"));
            Console.ReadLine();
        }
    }

    public class Container
    {
        public T InstantiateType<T>(string firstName, string lastName) where T : IPerson
        {
            T obj = T();
            obj.FirstName(firstName);
            obj.LastName(lastName);
            return obj;
        }

    }

    public interface IPerson
    {
        string FirstName { get; set; }
        string LastName { get; set; }
    }

    public class Customer : IPerson
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Company { get; set; }
    }

    public class Employee : IPerson
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int EmployeeNumber { get; set; }
    }
}

ПЕРЕФАКТОРНЫЙ ОТВЕТ:

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

using System;

namespace TestGeneric33
{
    class Program
    {
        static void Main(string[] args)
        {
            Container container = new Container();
            Customer customer1 = container.InstantiateType<Customer>("Jim", "Smith");
            Employee employee1 = container.InstantiateType<Employee>("Joe", "Thompson");
            Console.WriteLine(PersonDisplayer.SimpleDisplay(customer1));
            Console.WriteLine(PersonDisplayer.SimpleDisplay(employee1));
            Console.ReadLine();
        }
    }

    public class Container
    {
        public T InstantiateType<T>(string firstName, string lastName) where T : IPerson, new()
        {
            T obj = new T();
            obj.FirstName = firstName;
            obj.LastName = lastName;
            return obj;
        }
    }

    public interface IPerson
    {
        string FirstName { get; set; }
        string LastName { get; set; }
    }

    public class PersonDisplayer
    {
        private IPerson _person;

        public PersonDisplayer(IPerson person)
        {
            _person = person;
        }

        public string SimpleDisplay()
        {
            return String.Format("{1}, {0}", _person.FirstName, _person.LastName);
        }

        public static string SimpleDisplay(IPerson person)
        {
            PersonDisplayer personDisplayer = new PersonDisplayer(person);
            return personDisplayer.SimpleDisplay();
        }
    }

    public class Customer : IPerson
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Company { get; set; }
    }

    public class Employee : IPerson
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int EmployeeNumber { get; set; }
    }
}
Эдвард Тангуай
источник
+1 за переход к лучшему шаблону дизайна.
Joel Coehoorn
+1 за очень аккуратно набранный код, большая редкость.
nawfal

Ответы:

131

Объявите свой метод следующим образом:

public string InstantiateType<T>(string firstName, string lastName) 
              where T : IPerson, new()

Обратите внимание на дополнительное ограничение в конце. Затем создайте newэкземпляр в теле метода:

T obj = new T();    
Джоэл Кохорн
источник
4
Я писал на C # в течение многих лет с серьезным злоупотреблением типичной типизацией в свое время, и я НИКОГДА не знал, что вы можете определить подобное ограничение для создания экземпляра универсального типа. Большое спасибо!
Николас Мартель
очень очень хорошо!!
Sotiris Zegiannis
что, если НЕТ указанного типа, возможно ли это?
jj
31

Пара способов.

Без указания типа обязательно иметь конструктор:

T obj = default(T); //which will produce null for reference types

С конструктором:

T obj = new T();

Но для этого требуется пункт:

where T : new()
аннаката
источник
1
Первый будет назначать null, а не создавать экземпляр для ссылочных типов.
Joel Coehoorn
1
Ага. Вам необходимо использовать отражение для создания типов без конструктора по умолчанию, default (T) имеет значение null для всех ссылочных типов.
Дэн С.
1
Да, конечно, включено для полноты картины.
annakata
13

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

Если вы хотите избежать этого - а в шаблоне фабрики вы иногда заставляете других проходить через ваш фабричный метод, а не напрямую через конструктор - тогда альтернативой является использование Reflection ( Activator.CreateInstance...) и сохранение конструктора по умолчанию закрытым. Но это, конечно, приводит к снижению производительности.

Дэн С.
источник
Это не первый раз, когда люди голосуют против «всех остальных ответов» :)
Дэн С.
Я признаюсь, что иногда нецензурно не голосую за «конкурирующие» ответы, пока пыль не остановится на вопросе: Я полагаю, что (не-балльная) карма с ними справится!
Рубен Бартелинк,
8

вам нужен новый T (), но вам также необходимо добавить , new()в whereспецификацию для заводского метода

Рубен Бартелинк
источник
Я восстановил его, я понял это, помогло, похоже, в целом людям нравится размещенный код больше, чем описания здесь
Эдвард Тангуай
Спасибо, мир снова обретает смысл!
Рубен Бартелинк,
правильно, но ваш ответ, по общему признанию, немного короче;)
Лоренц Ло Зауэр,
4

Немного устарел, но для других, которые ищут решение, возможно, это может быть интересно: http://daniel.wertheim.se/2011/12/29/c-generic-factory-with-support-for-private-constructors/

Два решения. Один использует Activator, а другой - скомпилированные лямбды.

//Person has private ctor
var person = Factory<Person>.Create(p => p.Name = "Daniel");

public static class Factory<T> where T : class 
{
    private static readonly Func<T> FactoryFn;

    static Factory()
    {
        //FactoryFn = CreateUsingActivator();

        FactoryFn = CreateUsingLambdas();
    }

    private static Func<T> CreateUsingActivator()
    {
        var type = typeof(T);

        Func<T> f = () => Activator.CreateInstance(type, true) as T;

        return f;
    }

    private static Func<T> CreateUsingLambdas()
    {
        var type = typeof(T);

        var ctor = type.GetConstructor(
            BindingFlags.Instance | BindingFlags.CreateInstance |
            BindingFlags.NonPublic,
            null, new Type[] { }, null);

        var ctorExpression = Expression.New(ctor);
        return Expression.Lambda<Func<T>>(ctorExpression).Compile();
    }

    public static T Create(Action<T> init)
    {
        var instance = FactoryFn();

        init(instance);

        return instance;
    }
}
Даниэль
источник
2

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

var c = typeof(T).GetConstructor();
T t = (T)c.Invoke();
Pimbrouwers
источник
1

Использование фабричного класса для создания вашего объекта с помощью скомпилированного лямб-выражения: самый быстрый способ, который я нашел, для создания экземпляра универсального типа.

public static class FactoryContructor<T>
{
    private static readonly Func<T> New =
        Expression.Lambda<Func<T>>(Expression.New(typeof (T))).Compile();

    public static T Create()
    {
        return New();
    }
}

Вот шаги, которые я выполнил для настройки теста.

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

static void Benchmark(Action action, int iterationCount, string text)
{
    GC.Collect();
    var sw = new Stopwatch();
    action(); // Execute once before

    sw.Start();
    for (var i = 0; i <= iterationCount; i++)
    {
        action();
    }

    sw.Stop();
    System.Console.WriteLine(text + ", Elapsed: {0}ms", sw.ElapsedMilliseconds);
}

Я также пробовал использовать заводской метод:

public static T FactoryMethod<T>() where T : new()
{
    return new T();
}

Для тестов я создал простейший класс:

public class A { }

Скрипт для тестирования:

const int iterations = 1000000;
Benchmark(() => new A(), iterations, "new A()");
Benchmark(() => FactoryMethod<A>(), iterations, "FactoryMethod<A>()");
Benchmark(() => FactoryClass<A>.Create(), iterations, "FactoryClass<A>.Create()");
Benchmark(() => Activator.CreateInstance<A>(), iterations, "Activator.CreateInstance<A>()");
Benchmark(() => Activator.CreateInstance(typeof (A)), iterations, "Activator.CreateInstance(typeof (A))");

Результатов более 1000000 итераций:

новый A (): 11 мс

FactoryMethod A (): 275 мсек.

FactoryClass A .Create (): 56 мс

Activator.CreateInstance A (): 235 мс

Activator.CreateInstance (typeof (A)): 157 мс

Примечания : Я тестировал как .NET Framework 4.5, так и 4.6 (эквивалентные результаты).

Томас
источник
0

Вместо создания функции для создания экземпляра типа

public T InstantiateType<T>(string firstName, string lastName) where T : IPerson, new()
    {
        T obj = new T();
        obj.FirstName = firstName;
        obj.LastName = lastName;
        return obj;
    }

ты мог бы сделать это вот так

T obj = new T { FirstName = firstName, LastName = lastname };
TMul
источник
1
Это не отвечает на заданный вопрос. Настоящая проблема здесь заключалась в том, что ему нужно было создать новый экземпляр универсального класса. Возможно, это было непреднамеренно, но похоже, что вы говорите, что использование инициализатора решит исходную проблему, но это не так. new()Ограничение по - прежнему необходимо на общем типе для вашего ответа на работу.
Пользователь
Если вы пытаетесь быть полезным и предполагаете, что инициализатор является здесь полезным инструментом, вы должны опубликовать это как комментарий, а не как другой ответ.
Пользователь