C # 'da, bir yöntem içinde geçirilen genel bir tür nasıl başlatılır?


100

InstantiateType<T>Aşağıdaki yöntemimin içinde T tipini nasıl somutlaştırabilirim ?

Şu hatayı alıyorum: 'T' bir 'tip parametresi' ama 'değişken' gibi kullanılıyor. :

(REFAKTÖRLÜ CEVAP İÇİN AŞAĞI KAYDIRIN)

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

REFAKTÖRLÜ CEVAP:

Tüm yorumlar için teşekkürler, beni doğru yolda tuttular, yapmak istediğim şey buydu:

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

Daha iyi bir tasarım modeline geçmek için +1.
Joel Coehoorn

Son derece düzgün yazılmış kod için +1, nadirdir.
nawfal

Yanıtlar:


133

Yönteminizi şu şekilde beyan edin:

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

Sondaki ek kısıtlamaya dikkat edin. Ardından newyöntem gövdesinde bir örnek oluşturun :

T obj = new T();    

4
Yıllardır C # 'ı günlerimde bazı ağır genel yazım kötüye kullanımı ile yazıyorum ve genel bir türü başlatmak için böyle bir kısıtlama tanımlayabileceğinizi ASLA bilmiyordum. Çok teşekkürler!
Nicolas Martel

çok çok güzel!!
Sotiris Zegiannis

ya tip belirtilmemişse, bu mümkün mü?
jj

31

Birkaç yol.

Türü belirtmeden bir yapıcı olmalıdır:

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

Bir kurucu ile:

T obj = new T();

Ancak bu şu maddeyi gerektirir:

where T : new()

1
İlki, referans türleri için bir örnek oluşturmak yerine null atayacaktır.
Joel Coehoorn

1
Evet. Varsayılan bir kurucu olmadan türler oluşturmak için yansıma kullanmanız gerekir, varsayılan (T) tüm başvuru türleri için boştur.
Dan C.

1
Evet, kesinlikle, tamlık için dahil edildi.
annakata

13

Yukarıdaki yanıtları genişletmek için, where T:new()genel bir yönteme kısıt eklemek , T'nin genel, parametresiz bir kurucuya sahip olmasını gerektirir.

Bundan kaçınmak istiyorsanız - ve bir fabrika modelinde bazen diğerlerini doğrudan kurucu üzerinden değil fabrika yönteminizden geçmeye zorlarsanız - o zaman alternatif, yansıma ( Activator.CreateInstance...) kullanmak ve varsayılan kurucuyu özel tutmaktır. Ancak bu tabii ki bir performans cezasıyla birlikte geliyor.


İnsanların "diğer tüm yanıtlara" ilk kez olumsuz oy vermesi değil :)
Dan C.

Bazen, dusgt bir soruya karar verene kadar 'rekabet eden' cevapları titizlikle yükseltmediğimi itiraf edeceğim: DI tahmin et (puan olmayan) karma bunları çözecektir!
Ruben Bartelink

8

yeni T () istiyorsunuz , ancak fabrika yöntemi , new()için wherespesifikasyona eklemeniz gerekecek


tekrar çarptım, anladım, yardım ettim, genel olarak insanlar buradaki açıklamalardan daha iyi gönderilen kodu
seviyor

Teşekkürler, dünya yine mantıklı!
Ruben Bartelink

doğru ama cevabınız kuşkusuz kısa tarafta;)
Lorenz Lo Sauer

4

Biraz eski ama bir çözüm arayan diğerleri için belki bu ilgi çekici olabilir: http://daniel.wertheim.se/2011/12/29/c-generic-factory-with-support-for-private-constructors/

İki çözüm. Biri Aktivatör kullanıyor ve biri Derlenmiş Lambdalar kullanıyor.

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

Ayrıca, nesnenin yapıcısını getirmek ve şu şekilde somutlaştırmak için yansımayı kullanabilirsiniz:

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

1

Derlenmiş lamba ifadesiyle nesnenizi oluşturmak için bir fabrika sınıfı kullanmak: Jenerik türü somutlaştırmanın en hızlı yolu.

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

Karşılaştırma ölçütü oluşturmak için izlediğim adımlar.

Karşılaştırma testi yöntemimi oluştur:

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

Ayrıca bir fabrika yöntemi kullanmayı denedim:

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

Testler için en basit sınıfı oluşturdum:

public class A { }

Test edilecek komut dosyası:

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

1 000 000'den fazla yineleme sonuçları:

yeni A (): 11ms

Fabrika Yöntemi A (): 275 ms

Fabrika Sınıfı A. Oluşturma (): 56ms

Aktivatör A () Oluşturma Durumu: 235 ms

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

Açıklamalar : Hem .NET Framework 4.5 hem de 4.6 (eşdeğer sonuçlar) kullanarak test ettim .


0

Türü örneklemek için bir işlev oluşturmak yerine

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

bunu böyle yapabilirdin

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

1
Bu sorulan soruya cevap vermiyor. Buradaki asıl sorun, genel sınıfın yeni bir örneğini yaratması gerektiğiydi. Belki kasıtlı değildi, ama bir başlatıcı kullanmanın orijinal sorunu çözeceğini söylüyormuşsunuz gibi görünüyor, ama çözmüyor. new()Kısıtlamasının yine işe Cevabınız için jenerik türüne ihtiyaç vardır.
Kullanıcı

Yardımcı olmaya çalışıyorsanız ve burada başlatıcının yararlı bir araç olduğunu öneriyorsanız, bunu başka bir yanıt olarak değil, bir yorum olarak göndermelisiniz.
Kullanıcı
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.