W C #, jak utworzyć wystąpienie przekazanego typu ogólnego w metodzie?

98

Jak mogę utworzyć wystąpienie typu T w mojej InstantiateType<T>metodzie poniżej?

Pojawia się błąd: „T” to „parametr typu”, ale jest używany jako „zmienna”. :

(PRZEWIŃ W DÓŁ, ​​ABY ODPOWIEDZIĆ NA PONOWNIE)

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

ODPOWIEDŹ ODNOWIONA:

Dzięki za wszystkie komentarze, doprowadzili mnie do właściwej ścieżki, oto co chciałem zrobić:

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
źródło
+1 za przejście do lepszego wzorca projektowego.
Joel Coehoorn
+1 za wyjątkowo starannie wpisany kod, rzadkość.
nawfal

Odpowiedzi:

131

Zadeklaruj swoją metodę w następujący sposób:

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

Zwróć uwagę na dodatkowe ograniczenie na końcu. Następnie utwórz newinstancję w treści metody:

T obj = new T();    
Joel Coehoorn
źródło
4
Piszę w języku C # od lat, często nadużywając typowego pisania na klawiaturze w moich czasach i NIGDY nie wiedziałem, że możesz zdefiniować takie ograniczenie, aby utworzyć wystąpienie typu ogólnego. Wielkie dzięki!
Nicolas Martel
bardzo bardzo dobrze!!
Sotiris Zegiannis
co jeśli NIE określono typu, czy to możliwe?
jj
31

Na kilka sposobów.

Bez określenia typu musi mieć konstruktora:

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

Z konstruktorem:

T obj = new T();

Ale to wymaga klauzuli:

where T : new()
annakata
źródło
1
Pierwsza z nich przypisze wartość null, a nie utworzy instancję dla typów referencyjnych.
Joel Coehoorn
1
Tak. Musisz użyć odbicia, aby utworzyć typy bez domyślnego konstruktora, default (T) ma wartość null dla wszystkich typów odwołań.
Dan C.
1
Tak, absolutnie, dołączone dla kompletności.
annakata,
13

Aby rozszerzyć powyższe odpowiedzi, dodanie where T:new()ograniczenia do metody ogólnej będzie wymagało od T posiadania publicznego konstruktora bez parametrów.

Jeśli chcesz tego uniknąć - i zgodnie ze wzorcem fabrycznym czasami zmuszasz innych do przejścia przez twoją metodę fabryczną, a nie bezpośrednio przez konstruktora - alternatywą jest użycie refleksji ( Activator.CreateInstance...) i zachowanie domyślnego konstruktora jako prywatnego. Ale wiąże się to oczywiście ze spadkiem wydajności.

Dan C.
źródło
To nie pierwszy raz, kiedy ludzie negatywnie oceniają „wszystkie inne odpowiedzi” :)
Dan C.
Przyznam się, że czasami zuchwale nie głosowałem za „konkurującymi” odpowiedziami, dopóki nie zdecyduje się na pytanie: D Chyba (bez punktów) karma je rozwiąże!
Ruben Bartelink
8

chcesz nowe T (), ale musisz też dodać , new()do wherespecyfikacji dla metody fabrycznej

Ruben Bartelink
źródło
zepsułem to z powrotem, zrozumiałem, pomogłem, wydaje się, że ogólnie ludzie lubią opublikowany kod lepiej niż opisy tutaj
Edward Tanguay
Dzięki, świat znów nabiera sensu!
Ruben Bartelink
poprawna, ale twoja odpowiedź jest wprawdzie trochę krótka;)
Lorenz Lo Sauer
4

Trochę stary, ale dla innych szukających rozwiązania, może to być interesujące: http://daniel.wertheim.se/2011/12/29/c-generic-factory-with-support-for-private-constructors/

Dwa rozwiązania. Jeden wykorzystujący Activator i jeden wykorzystujący skompilowane lambdy.

//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
źródło
2

Możesz również użyć odbicia, aby pobrać konstruktor obiektu i utworzyć jego wystąpienie w ten sposób:

var c = typeof(T).GetConstructor();
T t = (T)c.Invoke();
pimbrouwers
źródło
1

Użycie klasy fabrycznej do zbudowania obiektu za pomocą skompilowanego wyrażenia lamba: najszybszy sposób, jaki znalazłem na tworzenie instancji typu ogólnego.

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

Oto kroki, które wykonałem, aby skonfigurować test porównawczy.

Utwórz moją metodę testową:

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

Próbowałem też użyć metody fabrycznej:

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

Do testów stworzyłem najprostszą klasę:

public class A { }

Skrypt do przetestowania:

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

Wyniki powyżej 1 000 000 iteracji:

nowy A (): 11 ms

FactoryMethod A (): 275 ms

FactoryClass A .Create (): 56 ms

Activator.CreateInstance A (): 235 ms

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

Uwagi : Testowałem używając zarówno .NET Framework 4.5, jak i 4.6 (równoważne wyniki).

Tomasz
źródło
0

Zamiast tworzyć funkcję do tworzenia wystąpienia typu

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

mogłeś to zrobić w ten sposób

T obj = new T { FirstName = firstName, LastName = lastname };
TMul
źródło
1
To nie odpowiada na zadane pytanie. Prawdziwy problem polegał na tym, że musiał utworzyć nową instancję klasy generycznej. Być może było to niezamierzone, ale wydaje się, że mówisz, że użycie inicjatora rozwiązałoby pierwotny problem, ale tak nie jest. Aby new()Twoja odpowiedź zadziałała, nadal konieczne jest ograniczenie typu ogólnego.
Użytkownik
Jeśli starasz się być pomocny i sugerujesz, że inicjator jest tutaj przydatnym narzędziem, powinieneś opublikować to jako komentarz, a nie kolejną odpowiedź.
Użytkownik