109 votes

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

Comment puis-je instancier le type T à l'intérieur de mes InstantiateType<T> méthode ci-dessous ?

Je reçois l'erreur : T est un "paramètre de type" mais est utilisé comme une "variable". :

(FAIRE DÉFILER VERS LE BAS POUR LA RÉPONSE REMANIÉ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 REMANIÉE :

Merci pour tous les commentaires, ils m'ont mis sur la bonne voie, c'est 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; }
    }
}

0 votes

+1 pour passer à un meilleur modèle de conception.

0 votes

+1 pour un code extrêmement bien typé, une rareté.

149voto

Joel Coehoorn Points 190579

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 un new dans le corps de la méthode :

T obj = new T();

6 votes

J'écris du C# depuis des années, avec quelques abus de typage générique, et je n'ai JAMAIS su que l'on pouvait définir une contrainte comme celle-ci pour instancier un type générique. Merci beaucoup !

0 votes

Très très beau !

0 votes

Et si AUCUN type n'est spécifié, est-ce possible ?

33voto

annakata Points 42676

De plusieurs 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()

1 votes

La première assignera null plutôt que de créer une instance pour les types de référence.

1 votes

Yep. Vous devez utiliser la réflexion pour créer des types sans constructeur par défaut, default(T) est nul pour tous les types de référence.

1 votes

Oui, absolument, inclus pour être complet.

14voto

Dan C. Points 2115

Pour approfondir les réponses ci-dessus, ajouter where T:new() à une méthode générique exigera que T ait un constructeur public, sans paramètre.

Si vous voulez éviter cela - et dans un modèle de fabrique, vous forcez parfois les autres à passer par votre méthode de fabrique 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.

0 votes

Ce n'est pas la première fois que les gens décotent "toutes les autres réponses" :)

0 votes

J'admets qu'il m'arrive parfois de ne pas upvoter les réponses "concurrentes" jusqu'à ce que la victime ait choisi une question :D J'imagine que le karma (sans points) fera le tri !

8voto

Ruben Bartelink Points 23945

Vous voulez nouveau T(), mais vous devrez également ajouter la fonction , new() à la where spécification de la méthode d'usine

0 votes

Je l'ai remonté, je l'ai compris, aidé, il semble qu'en général les gens aiment mieux le code affiché que les descriptions ici

1 votes

Merci, le monde a retrouvé un sens !

0 votes

Correct mais votre réponse est certes un peu courte ;)

5voto

Daniel Points 2657

C'est un peu vieux mais pour les autres qui cherchent une solution, cela pourrait peut-être les intéresser : http://daniel.wertheim.se/2011/12/29/c-generic-factory-with-support-for-private-constructors/

Deux solutions. L'une utilisant Activator et l'autre utilisant 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;
    }
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X