In C #, come creare un'istanza di un tipo generico passato all'interno di un metodo?


98

Come posso istanziare il tipo T nel mio InstantiateType<T>metodo di seguito?

Ricevo l'errore: "T" è un "parametro di tipo" ma viene utilizzato come una "variabile". :

(SCORRI VERSO IL BASSO PER UNA RISPOSTA RIFATTURATA)

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

RISPOSTA RIFATTURATA:

Grazie per tutti i commenti, mi hanno messo sulla strada giusta, questo è quello che volevo fare:

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 per passare a un modello di progettazione migliore.
Joel Coehoorn

+1 per codice digitato in modo estremamente accurato, una rarità.
nawfal

Risposte:


131

Dichiara il tuo metodo in questo modo:

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

Notare il vincolo aggiuntivo alla fine. Quindi crea newun'istanza nel corpo del metodo:

T obj = new T();    

4
Ho scritto C # per anni con un pesante abuso di tipizzazione generica ai miei tempi e non sapevo MAI che potresti definire un vincolo come questo per istanziare un tipo generico. Grazie molto!
Nicolas Martel

molto molto carino!!
Sotiris Zegiannis

cosa succede se NESSUN tipo specificato, è possibile?
jj

31

Un paio di modi.

Senza specificare il tipo deve avere un costruttore:

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

Con un costruttore:

T obj = new T();

Ma questo richiede la clausola:

where T : new()

1
Il primo assegnerà null anziché creare un'istanza per i tipi di riferimento.
Joel Coehoorn

1
Sì. È necessario utilizzare la reflection per creare tipi senza un costruttore predefinito, default (T) è null per tutti i tipi di riferimento.
Dan C.

1
Sì assolutamente, incluso per completezza davvero.
annakata

13

Per estendere le risposte precedenti, l'aggiunta di where T:new()vincoli a un metodo generico richiederà che T abbia un costruttore pubblico e senza parametri.

Se vuoi evitarlo - e in un pattern factory a volte costringi gli altri a passare attraverso il tuo metodo factory e non direttamente attraverso il costruttore - allora l'alternativa è usare reflection ( Activator.CreateInstance...) e mantenere privato il costruttore predefinito. Ma questo comporta una penalizzazione delle prestazioni, ovviamente.


Non è la prima volta che le persone votano "tutte le altre risposte" :)
Dan C.

Devo ammettere che a volte scurrilemente non votare risposte "concorrenti" fino a quando il dusgt non ha risolto una domanda: D suppongo che il karma (non punti) le risolva!
Ruben Bartelink

8

vuoi new T (), ma dovrai anche aggiungere , new()alle wherespecifiche per il metodo factory


l'ho ripristinato, l'ho capito, aiutato, sembra che in generale alle persone piaccia il codice pubblicato meglio delle descrizioni qui
Edward Tanguay

Grazie, il mondo ha di nuovo un senso!
Ruben Bartelink

corretto ma la tua risposta è certamente un po 'corta;)
Lorenz Lo Sauer

4

Un po 'vecchio ma per altri che cercano una soluzione, forse questo potrebbe interessare: http://daniel.wertheim.se/2011/12/29/c-generic-factory-with-support-for-private-constructors/

Due soluzioni. Uno che utilizza Activator e uno che utilizza Lambdas compilati.

//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

Puoi anche usare la reflection per recuperare il costruttore dell'oggetto e creare un'istanza in questo modo:

var c = typeof(T).GetConstructor();
T t = (T)c.Invoke();

1

Utilizzo di una classe factory per creare il tuo oggetto con un'espressione lamba compilata: il modo più veloce che ho trovato per istanziare un tipo generico.

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

Ecco i passaggi che ho seguito per impostare il benchmark.

Crea il mio metodo di test benchmark:

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

Ho anche provato a utilizzare un metodo di fabbrica:

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

Per i test ho creato la classe più semplice:

public class A { }

Lo script da testare:

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

Risultati oltre 1.000.000 di iterazioni:

nuovo LA (): 11 ms

FactoryMethod A (): 275 ms

FactoryClass A .Create (): 56 ms

Activator.CreateInstance A (): 235ms

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

Note : ho testato utilizzando sia .NET Framework 4.5 che 4.6 (risultati equivalenti).


0

Invece di creare una funzione per istanziare il tipo

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

avresti potuto farlo in questo modo

T obj = new T { FirstName = firstName, LastName = lastname };

1
Questo non risponde alla domanda che viene posta. Il vero problema qui era che aveva bisogno di creare una nuova istanza della classe generica. Forse non era intenzionale, ma sembra che tu stia dicendo che l'uso di un inizializzatore risolverebbe il problema originale, ma non lo fa. Il new()vincolo è ancora necessario per il tipo generico affinché la tua risposta funzioni.
Utente

Se stai cercando di essere utile e suggerisci che l'inizializzatore è uno strumento utile qui, dovresti pubblicarlo come commento, non come un'altra risposta.
Utente
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.