Как в 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();

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

1
Это не отвечает на заданный вопрос. Настоящая проблема здесь заключалась в том, что ему нужно было создать новый экземпляр универсального класса. Возможно, это было непреднамеренно, но похоже, что вы говорите, что использование инициализатора решит исходную проблему, но это не так. new()Ограничение по - прежнему необходимо на общем типе для вашего ответа на работу.
— Пользователь

Если вы пытаетесь быть полезным и предполагаете, что инициализатор является здесь полезным инструментом, вы должны опубликовать это как комментарий, а не как другой ответ.
— Пользователь
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.