Wie kann in C # ein übergebener generischer Typ innerhalb einer Methode instanziiert werden?

98

Wie kann ich den Typ T in meiner folgenden InstantiateType<T>Methode instanziieren ?

Ich erhalte die Fehlermeldung: 'T' ist ein 'Typparameter', wird aber wie eine 'Variable' verwendet. ::

(SCROLLEN SIE FÜR REFACTORED ANTWORT NACH UNTEN)

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

REFAKTORIERTE ANTWORT:

Vielen Dank für alle Kommentare, sie haben mich auf den richtigen Weg gebracht, das wollte ich tun:

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; }
    }
}
Edward Tanguay
quelle
+1 für die Umstellung auf ein besseres Entwurfsmuster.
Joel Coehoorn
+1 für extrem ordentlich getippten Code, eine Seltenheit.
Nawfal

Antworten:

131

Deklarieren Sie Ihre Methode folgendermaßen:

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

Beachten Sie die zusätzliche Einschränkung am Ende. Erstellen Sie dann eine newInstanz im Methodenkörper:

T obj = new T();    
Joel Coehoorn
quelle
4
Ich schreibe seit Jahren C # mit einem starken Missbrauch generischer Typisierung in meinen Tagen, und ich wusste NIE, dass Sie eine solche Einschränkung definieren können, um einen generischen Typ zu instanziieren. Danke vielmals!
Nicolas Martel
sehr sehr nett!!
Sotiris Zegiannis
Was ist, wenn KEIN Typ angegeben ist? Ist das möglich?
jj
31

Ein paar Möglichkeiten.

Ohne Angabe des Typs muss ein Konstruktor vorhanden sein:

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

Mit einem Konstruktor:

T obj = new T();

Dies erfordert jedoch die Klausel:

where T : new()
Annakata
quelle
1
Der erste weist null zu, anstatt eine Instanz für Referenztypen zu erstellen.
Joel Coehoorn
1
Ja. Sie müssen Reflection verwenden, um Typen ohne Standardkonstruktor zu erstellen. Standard (T) ist für alle Referenztypen null.
Dan C.
1
Ja absolut, der Vollständigkeit halber wirklich enthalten.
Annakata
13

Um die obigen Antworten zu erweitern, where T:new()erfordert das Hinzufügen einer Einschränkung zu einer generischen Methode, dass T einen öffentlichen, parameterlosen Konstruktor hat.

Wenn Sie dies vermeiden möchten - und in einem Factory-Muster manchmal die anderen dazu zwingen, Ihre Factory-Methode und nicht direkt den Konstruktor zu durchlaufen -, besteht die Alternative darin, Reflection ( Activator.CreateInstance...) zu verwenden und den Standardkonstruktor privat zu halten. Aber das bringt natürlich eine Leistungsstrafe mit sich.

Dan C.
quelle
Es ist nicht das erste Mal, dass Leute "alle anderen Antworten" ablehnen :)
Dan C.
Ich gebe zu, dass ich manchmal skurril keine "konkurrierenden" Antworten stimme, bis sich die Dämmerung auf eine Frage geeinigt hat: DI rate (Nicht-Punkte) Karma wird sie klären!
Ruben Bartelink
8

Sie möchten ein neues T (), müssen aber auch , new()die whereSpezifikation für die Factory-Methode ergänzen

Ruben Bartelink
quelle
Ich habe es wieder hochgestoßen, ich habe es verstanden, geholfen, es scheint, dass Leute im Allgemeinen den geposteten Code besser mögen als die Beschreibungen hier
Edward Tanguay
Danke, die Welt macht wieder Sinn!
Ruben Bartelink
richtig, aber deine Antwort ist zugegebenermaßen etwas kurz;)
Lorenz Lo Sauer
4

Ein bisschen alt, aber für andere, die nach einer Lösung suchen, könnte dies vielleicht von Interesse sein: http://daniel.wertheim.se/2011/12/29/c-generic-factory-with-support-for-private-constructors/

Zwei Lösungen. Eine mit Activator und eine mit Compiled Lambdas.

//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;
    }
}
Daniel
quelle
2

Sie können auch Reflection verwenden, um den Konstruktor des Objekts abzurufen und auf diese Weise zu instanziieren:

var c = typeof(T).GetConstructor();
T t = (T)c.Invoke();
Zuhälter
quelle
1

Verwenden einer Factory-Klasse zum Erstellen Ihres Objekts mit kompiliertem Lamba-Ausdruck: Der schnellste Weg, den generischen Typ zu instanziieren.

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

Hier sind die Schritte, die ich ausgeführt habe, um den Benchmark festzulegen.

Erstellen Sie meine Benchmark-Testmethode:

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

Ich habe auch versucht, eine Factory-Methode zu verwenden:

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

Für die Tests habe ich die einfachste Klasse erstellt:

public class A { }

Das zu testende Skript:

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

Ergebnisse über 1 000 000 Iterationen:

neues A (): 11ms

FactoryMethod A (): 275 ms

FactoryClass A .Create (): 56 ms

Activator.CreateInstance A (): 235 ms

Activator.CreateInstance (Typ von (A)): 157 ms

Anmerkungen : Ich habe sowohl .NET Framework 4.5 als auch 4.6 getestet (äquivalente Ergebnisse).

Thomas
quelle
0

Anstatt eine Funktion zum Instanziieren des Typs zu erstellen

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

du hättest es so machen können

T obj = new T { FirstName = firstName, LastName = lastname };
TMul
quelle
1
Dies beantwortet nicht die gestellte Frage. Das eigentliche Problem dabei war, dass er eine neue Instanz der generischen Klasse erstellen musste. Vielleicht war es unbeabsichtigt, aber es scheint, als würden Sie sagen, dass die Verwendung eines Initialisierers das ursprüngliche Problem lösen würde, aber dies ist nicht der Fall. Die new()Einschränkung für den generischen Typ wird weiterhin benötigt, damit Ihre Antwort funktioniert.
Benutzer
Wenn Sie versuchen, hilfreich zu sein und vorschlagen, dass der Initialisierer hier ein nützliches Werkzeug ist, sollten Sie dies als Kommentar und nicht als weitere Antwort veröffentlichen.
Benutzer