Як в 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.