Як в C # як інстанціювати переданий загальний тип всередині методу?


98

Як я можу описати тип 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 # протягом багатьох років із важким загальним зловживанням під час введення тексту, і я ніколи не знав, що ти можеш визначити таке обмеження, щоб створити загальний тип. Велике дякую!
Ніколя Мартел

дуже-дуже хороший!!
Сотіріс Зегіаніс

що робити, якщо НЕ вказаний тип, чи це можливо?
jj

31

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

Не вказуючи тип, повинен мати конструктор:

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

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

T obj = new T();

Але для цього потрібно пункт:

where T : new()

1
Перший призначатиме нульовий, а не створює екземпляр для типів посилань.
Joel Coehoorn

1
Так. Потрібно використовувати відображення для створення типів без конструктора за замовчуванням, для всіх типів посилань default (T) є нульовим.
Dan C.

1
Так, включено для повноти дійсно.
annakata

13

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

Якщо ви хочете цього уникнути - і за фабричним малюнком ви іноді змушуєте інших пройти ваш заводський метод, а не безпосередньо через конструктор - тоді альтернативою є використання відображення ( Activator.CreateInstance...) та збереження приватного конструктора за замовчуванням. Але це, звичайно, приносить штрафний показник.


Це не перший раз, коли люди зволікають "на всі інші відповіді" :)
Dan C.

Я визнаю, що іноді скрупульозно не оскаржуючи «конкуруючих» відповідей, поки дусст не вирішиться на питання: DI гадаю (неточкові) карми розберуть їх!
Рубен Бартелінк

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

Результати понад 1 000 000 повторень:

новий A (): 11ms

FactoryMethod A (): 275 мс

FactoryClass A .Create (): 56ms

Activator.CreateInstance A (): 235ms

Activator.CreateInstance (typeof (A)): 157ms

Зауваження . Я перевірив, використовуючи обидва .NET Framework 4.5 і 4.6 (еквівалентні результати).


0

Замість створення функції Instantiate типу

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.