Em C #, como instanciar um tipo genérico passado dentro de um método?

98

Como posso instanciar o tipo T dentro do meu InstantiateType<T>método abaixo?

Estou recebendo o erro: 'T' é um 'parâmetro de tipo', mas é usado como uma 'variável'. :

(Role para baixo para obter uma resposta refaturada)

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

RESPOSTA REFATORADA:

Obrigado por todos os comentários, eles me colocaram no caminho certo, isso é o que eu queria fazer:

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
fonte
1 para mudar para um melhor padrão de design.
Joel Coehoorn
+1 para código digitado com extrema precisão, uma raridade.
nawfal

Respostas:

131

Declare seu método assim:

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

Observe a restrição adicional no final. Em seguida, crie uma newinstância no corpo do método:

T obj = new T();    
Joel Coehoorn
fonte
4
Eu tenho escrito C # por anos com algum abuso pesado de tipagem genérica em meus dias, e eu NUNCA soube que você poderia definir uma restrição como essa para instanciar um tipo genérico. Muito Obrigado!
Nicolas Martel
muito muito bom!!
Sotiris Zegiannis de
e se nenhum tipo especificado, isso é possível?
jj
31

De várias maneiras.

Sem especificar o tipo deve ter um construtor:

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

Com um construtor:

T obj = new T();

Mas isso requer a cláusula:

where T : new()
Annakata
fonte
1
O primeiro atribuirá null em vez de criar uma instância para tipos de referência.
Joel Coehoorn
1
Sim. Você precisa usar reflexão para criar tipos sem um construtor padrão, default (T) é nulo para todos os tipos de referência.
Dan C.
1
Sim, absolutamente, incluído para ser completo.
annakata
13

Para ampliar as respostas acima, adicionar where T:new()restrição a um método genérico exigirá que T tenha um construtor público sem parâmetros.

Se você quiser evitar isso - e em um padrão de fábrica, às vezes você força os outros a passarem pelo seu método de fábrica e não diretamente pelo construtor - então a alternativa é usar reflexão ( Activator.CreateInstance...) e manter o construtor padrão privado. Mas isso vem com uma penalidade de desempenho, é claro.

Dan C.
fonte
Não é a primeira vez que as pessoas votam negativamente em "todas as outras respostas" :)
Dan C.
Admito que, às vezes, cruelmente, não votei positivamente nas respostas "concorrentes" até que o cara se decidisse sobre uma questão: D acho que o carma (sem pontos) vai resolvê-los!
Ruben Bartelink
8

você quer novo T (), mas também precisa adicionar , new()à whereespecificação do método de fábrica

Ruben Bartelink
fonte
aumentei, entendi, ajudei, parece que em geral as pessoas gostam mais de código postado do que de descrições aqui
Edward Tanguay
Obrigado, o mundo faz sentido novamente!
Ruben Bartelink,
correto, mas sua resposta é um pouco curta;)
Lorenz Lo Sauer
4

Um pouco antigo, mas para quem procura uma solução, talvez isso possa ser do seu interesse: http://daniel.wertheim.se/2011/12/29/c-generic-factory-with-support-for-private-constructors/

Duas soluções. Um usando Activator e outro usando 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
fonte
2

Você também pode usar reflexão para buscar o construtor do objeto e instanciar dessa forma:

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

Usando uma classe de fábrica para construir seu objeto com a expressão lamba compilada: A maneira mais rápida que encontrei de instanciar o tipo genérico.

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

Aqui estão as etapas que segui para configurar o benchmark.

Crie meu método de teste de referência:

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

Também tentei usar um método de fábrica:

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

Para os testes, criei a classe mais simples:

public class A { }

O script para testar:

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

Resultados ao longo de 1 000 000 iterações:

novo A (): 11ms

FactoryMethod A (): 275ms

FactoryClass A .Create (): 56ms

Activator.CreateInstance A (): 235ms

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

Observações : Eu testei usando o .NET Framework 4.5 e 4.6 (resultados equivalentes).

Thomas
fonte
0

Em vez de criar uma função para instanciar o 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;
    }

você poderia ter feito assim

T obj = new T { FirstName = firstName, LastName = lastname };
TMul
fonte
1
Isso não responde à pergunta que está sendo feita. O verdadeiro problema aqui era que ele precisava criar uma nova instância da classe genérica. Talvez não tenha sido intencional, mas parece que você está dizendo que usar um inicializador resolveria o problema original, mas não resolve. A new()restrição ainda é necessária no tipo genérico para que sua resposta funcione.
Usuário
Se você está tentando ser útil e sugerir que o inicializador é uma ferramenta útil aqui, você deve postar isso como um comentário, não como outra resposta.
Usuário