web-dev-qa-db-fra.com

En C #, comment instancier un type générique passé dans une méthode?

Comment puis-je instancier le type T dans mon InstantiateType<T> méthode ci-dessous?

J'obtiens l'erreur: 'T' est un 'paramètre de type' mais est utilisé comme une 'variable'.:

(DÉFILEZ VERS LE BAS POUR RÉPONDRE À UNE RÉPONSE RÉFACTORÉE)

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

RÉPONSE RÉFACTORÉE:

Merci pour tous les commentaires, ils m'ont mis sur la bonne voie, voici ce que je voulais faire:

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; }
    }
}
88
Edward Tanguay

Déclarez votre méthode comme ceci:

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

Remarquez la contrainte supplémentaire à la fin. Créez ensuite une instance new dans le corps de la méthode:

T obj = new T();    
108
Joel Coehoorn

Deux façons.

Sans spécifier le type doit avoir un constructeur:

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

Avec un constructeur:

T obj = new T();

Mais cela nécessite la clause:

where T : new()
27
annakata

Pour étendre les réponses ci-dessus, l'ajout de la contrainte where T:new() à une méthode générique nécessitera que T ait un constructeur public sans paramètre.

Si vous voulez éviter cela - et dans un modèle d'usine, vous forcez parfois les autres à passer par votre méthode d'usine et non directement par le constructeur - alors l'alternative est d'utiliser la réflexion (Activator.CreateInstance...) et garder le constructeur par défaut privé. Mais cela s'accompagne d'une pénalité de performance, bien sûr.

13
Dan C.

vous voulez nouvea T (), mais vous devrez également ajouter , new() à la spécification where pour la méthode d'usine

8
Ruben Bartelink

Un peu vieux mais pour ceux qui recherchent une solution, cela pourrait peut-être être intéressant: http://daniel.wertheim.se/2011/12/29/c-generic-factory-with-support-for- constructeurs-privés /

Deux solutions. Un utilisant Activator et un utilisant Lambdas compilés.

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

Au lieu de créer une fonction pour instancier le type

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

tu aurais pu le faire comme ça

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

Vous pouvez également utiliser la réflexion pour récupérer le constructeur de l'objet et instancier de cette façon:

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

Utiliser une classe d'usine pour construire votre objet avec une expression lamba compilée: Le moyen le plus rapide que j'ai trouvé pour instancier un type générique.

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

Voici les étapes que j'ai suivies pour établir la référence.

Créer ma méthode de test de référence:

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

J'ai également essayé d'utiliser une méthode d'usine:

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

Pour les tests, j'ai créé la classe la plus simple:

public class A { }

Le script à tester:

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

Résultats sur 1 000 000 d'itérations:

nouveau A (): 11ms

Méthode d'usine A (): 275 ms

FactoryClass A .Create (): 56 ms

Activator.CreateInstance A (): 235ms

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

Remarques: J'ai testé en utilisant les deux . NET Framework 4.5 et 4.6 (résultats équivalents).

0
Thomas