Şablonlu türün C # generic new () değişkenine iletilmesi


409

Listeye eklerken yapıcısı üzerinden T türü yeni bir nesne oluşturmaya çalışıyorum.

Derleme hatası alıyorum: Hata iletisi:

'T': değişken örneği oluştururken argüman sağlayamaz

Ama sınıflarımın yapıcı bir argümanı var! Bunu nasıl yapabilirim?

public static string GetAllItems<T>(...) where T : new()
{
   ...
   List<T> tabListItems = new List<T>();
   foreach (ListItem listItem in listCollection) 
   {
       tabListItems.Add(new T(listItem)); // error here.
   } 
   ...
}


2
Bu işlevi dile getirmek için teklif: github.com/dotnet/roslyn/issues/2206
Ian Kemp

Microsoft'un belgelerinde, Derleyici Hatası CS0417'ye bakın .
DavidRR

1
Bu işlevselliği dile getirmek için teklif şu adrese
etkinliği azaltma

Yanıtlar:


410

Bir işlevde genel türün bir örneğini oluşturmak için, onu "yeni" bayrağıyla sınırlamanız gerekir.

public static string GetAllItems<T>(...) where T : new()

Ancak bu, yalnızca parametresi olmayan yapıcıyı çağırmak istediğinizde işe yarayacaktır. Burada durum böyle değil. Bunun yerine, parametrelere dayalı olarak nesnenin oluşturulmasına izin veren başka bir parametre sağlamanız gerekir. En kolayı bir işlevdir.

public static string GetAllItems<T>(..., Func<ListItem,T> del) {
  ...
  List<T> tabListItems = new List<T>();
  foreach (ListItem listItem in listCollection) 
  {
    tabListItems.Add(del(listItem));
  }
  ...
}

Daha sonra böyle diyebilirsiniz

GetAllItems<Foo>(..., l => new Foo(l));

Bu, genel bir sınıftan dahili olarak çağrıldığında nasıl çalışır? Kodumu aşağıdaki cevaba gönderdim. Genel bir sınıf olduğu için içten somut sınıfı bilmiyorum. Bunun bir yolu var mı? Ben
yapıcıda

kodumu başka bir soruya ekledi stackoverflow.com/questions/1682310/…
ChrisCa

21
Bu şu anda C # en sinir bozucu sınırlamalardan biridir. Sınıflarımı değişmez kılmak istiyorum: Sadece özel ayarlayıcılara sahip olmak, sınıfın yan etkilerle geçersiz bir durumda olmasını imkansız hale getirecektir. Ayrıca Func ve lambda'yı kullanmayı seviyorum, ancak genellikle programcılar henüz lambdaları bilmediğinden ve bu da sınıfınızın anlaşılmasını zorlaştırdığı için iş dünyasında hala bir sorun olduğunu biliyorum.
Tuomas Hietanen

1
Teşekkürler. Benim durumumda yöntemi çağırdığımda yapıcı argüman (lar) ı biliyorum, sadece parametrelerle inşa edilemediğinden Tür parametresinin sınırlamasını aşmam gerekiyordu , bu yüzden bir thunk kullandım . Thunk, yöntemin isteğe bağlı bir parametresidir ve yalnızca sağlandıysa kullanırım: T result = thunk == null ? new T() : thunk(); Bunun yararı, benim için Tyaratılış mantığını, Tbazen yöntemin içinde bazen de dışında yaratmak yerine tek bir yerde birleştirmektir .
Carl G

Bence bu C # dilinin programcıya hayır demeye karar verdiği ve her zaman evet demeyi bıraktığı yerlerden biri! Bu yaklaşım, nesne yaratmanın biraz garip bir yolu olmasına rağmen, şimdilik kullanmam gerekiyor.
AmirHossein Rezaei

331

Net 3.5'te ve aktivatör sınıfını kullandıktan sonra:

(T)Activator.CreateInstance(typeof(T), args)

1
Ayrıca nesneyi oluşturmak için ifade ağacını da kullanabiliriz
Welly Tambunan

4
Args nedir? bir obje[]?
Rodney P. Barbati

3
Evet, args, T yapıcısına sağlanacak değerleri belirttiğiniz bir nesne [] 'dir: "new object [] {par1, par2}"
TechNyquist


3
UYARI: Sadece Activator.CreateInstancebu şey uğruna özel bir kurucunuz varsa, kurucunuz hiç kullanılmamış gibi görünecek ve biri "temizlemeye" ve silmeye çalışabilir ( gelecekte rastgele bir süre). Bu kurucuyu kullandığınız yere kukla bir işlev eklemeyi düşünebilirsiniz, böylece silmeye çalışırsanız derleme hatası alırsınız.
jrh

51

Kimse 'Yansıtma' cevabını (kişisel olarak en iyi cevap olduğunu düşünüyorum) göndermek için uğraşmadığından, işte gidiyor:

public static string GetAllItems<T>(...) where T : new()
{
   ...
   List<T> tabListItems = new List<T>();
   foreach (ListItem listItem in listCollection) 
   {
       Type classType = typeof(T);
       ConstructorInfo classConstructor = classType.GetConstructor(new Type[] { listItem.GetType() });
       T classInstance = (T)classConstructor.Invoke(new object[] { listItem });

       tabListItems.Add(classInstance);
   } 
   ...
}

Düzenleme: Bu yanıt .NET 3.5'in Activator.CreateInstance nedeniyle kullanımdan kaldırılmıştır, ancak yine de eski .NET sürümlerinde yararlıdır.


Anladığım kadarıyla, isabet edilen performansın büyük bir kısmı ConstructorInfo'yu ilk etapta elde etmek. Sözümü çıkarmadan sözümü alma. Bu durumda, ConstructorInfo öğesini daha sonra tekrar kullanmak üzere saklamak, yansıma yoluyla tekrarlanan örneklemelerin performans isabetini hafifletebilir.
Kelsie

19
Derleme zamanı kontrolünün olmaması endişe sebebi olduğunu düşünüyorum.
Dave Van den Eynde

1
@James Kabul ediyorum, bunu "cevap" olarak görmemeye şaşırdım. Aslında, yansımayı yaptığımdan bu yana çok uzun zaman geçtiğinden, sizinki gibi güzel ve kolay bir örnek bulmayı bekleyen bu soruyu araştırdım. Her neyse, benden +1, ama Aktivatör'e +1 de cevap veriyor. Aktivatörün ne yaptığını araştırdım ve yaptığı şeyin çok iyi tasarlanmış bir yansıma olduğu ortaya çıktı. :)
Mike

GetConstructor () çağrısı pahalıdır, bu nedenle döngüden önce önbelleğe almaya değer. Bu şekilde, döngü içinde yalnızca Invoke () öğesini çağırmak, her ikisini de çağırmaktan veya hatta Activator.CreateInstance () kullanmaktan çok daha hızlıdır.
Cosmin Rus

30

Nesne başlatıcı

Parametreye sahip kurucunuz bir özellik ayarlamanın yanı sıra hiçbir şey yapmıyorsa, bunu yapıcı çağırmak yerine (yukarıda belirtildiği gibi imkansızdır) yerine bir nesne başlatıcı kullanarak C # 3 veya daha iyi bir şekilde yapabilirsiniz:

public static string GetAllItems<T>(...) where T : new()
{
   ...
   List<T> tabListItems = new List<T>();
   foreach (ListItem listItem in listCollection) 
   {
       tabListItems.Add(new T() { YourPropertyName = listItem } ); // Now using object initializer
   } 
   ...
}

Bunu kullanarak, her zaman yapıcı mantığını varsayılan (boş) yapıcıya da koyabilirsiniz.

Activator.CreateInstance ()

Alternatif olarak, Activator.CreateInstance () yöntemini şöyle çağırabilirsiniz :

public static string GetAllItems<T>(...) where T : new()
{
   ...
   List<T> tabListItems = new List<T>();
   foreach (ListItem listItem in listCollection) 
   {
        object[] args = new object[] { listItem };
        tabListItems.Add((T)Activator.CreateInstance(typeof(T), args)); // Now using Activator.CreateInstance
   } 
   ...
}

Activator.CreateInstance uygulamasının, yürütme hızı en yüksek önceliğe sahipse ve sizin için başka bir seçenek korunabiliyorsa, kaçınmak isteyebileceğiniz bir performans yükü olabileceğini unutmayın.


bu Tdeğişmezlerini korumayı önler ( T> 0 bağımlılık veya gerekli değerlere sahip olduğu göz önüne alındığında , artık Tgeçersiz / kullanılamaz durumda olan örnekleri oluşturabilirsiniz .T .
sara

20

Çok eski bir soru, ama yeni cevap ;-)

ExpressionTree sürümü : (Bence en hızlı ve en temiz çözüm)

Gibi Welly Tambunan dedi "biz de nesneyi oluşturmak için ifade ağacı kullanabilirsiniz"

Bu, verilen tür / parametreler için bir 'yapıcı' (işlev) oluşturur. Bir temsilci döndürür ve parametre türlerini bir nesne dizisi olarak kabul eder.

İşte burada:

// this delegate is just, so you don't have to pass an object array. _(params)_
public delegate object ConstructorDelegate(params object[] args);

public static ConstructorDelegate CreateConstructor(Type type, params Type[] parameters)
{
    // Get the constructor info for these parameters
    var constructorInfo = type.GetConstructor(parameters);

    // define a object[] parameter
    var paramExpr = Expression.Parameter(typeof(Object[]));

    // To feed the constructor with the right parameters, we need to generate an array 
    // of parameters that will be read from the initialize object array argument.
    var constructorParameters = parameters.Select((paramType, index) =>
        // convert the object[index] to the right constructor parameter type.
        Expression.Convert(
            // read a value from the object[index]
            Expression.ArrayAccess(
                paramExpr,
                Expression.Constant(index)),
            paramType)).ToArray();

    // just call the constructor.
    var body = Expression.New(constructorInfo, constructorParameters);

    var constructor = Expression.Lambda<ConstructorDelegate>(body, paramExpr);
    return constructor.Compile();
}

Örnek Sınıfım:

public class MyClass
{
    public int TestInt { get; private set; }
    public string TestString { get; private set; }

    public MyClass(int testInt, string testString)
    {
        TestInt = testInt;
        TestString = testString;
    }
}

Kullanımı:

// you should cache this 'constructor'
var myConstructor = CreateConstructor(typeof(MyClass), typeof(int), typeof(string));

// Call the `myConstructor` function to create a new instance.
var myObject = myConstructor(10, "test message");

resim açıklamasını buraya girin


Başka bir örnek: türleri dizi olarak geçirme

var type = typeof(MyClass);
var args = new Type[] { typeof(int), typeof(string) };

// you should cache this 'constructor'
var myConstructor = CreateConstructor(type, args);

// Call the `myConstructor` fucntion to create a new instance.
var myObject = myConstructor(10, "test message");

Hata Ayıklama Görünümü

.Lambda #Lambda1<TestExpressionConstructor.MainWindow+ConstructorDelegate>(System.Object[] $var1) {
    .New TestExpressionConstructor.MainWindow+MyClass(
        (System.Int32)$var1[0],
        (System.String)$var1[1])
}

Bu, üretilen koda eşdeğerdir:

public object myConstructor(object[] var1)
{
    return new MyClass(
        (System.Int32)var1[0],
        (System.String)var1[1]);
}

Küçük dezavantaj

Tüm değer türleri parametreleri, bir nesne dizisi gibi iletildiklerinde kutlanır.


Basit performans testi:

private void TestActivator()
{
    Stopwatch sw = Stopwatch.StartNew();
    for (int i = 0; i < 1024 * 1024 * 10; i++)
    {
        var myObject = Activator.CreateInstance(typeof(MyClass), 10, "test message");
    }
    sw.Stop();
    Trace.WriteLine("Activator: " + sw.Elapsed);
}

private void TestReflection()
{
    var constructorInfo = typeof(MyClass).GetConstructor(new[] { typeof(int), typeof(string) });

    Stopwatch sw = Stopwatch.StartNew();
    for (int i = 0; i < 1024 * 1024 * 10; i++)
    {
        var myObject = constructorInfo.Invoke(new object[] { 10, "test message" });
    }

    sw.Stop();
    Trace.WriteLine("Reflection: " + sw.Elapsed);
}

private void TestExpression()
{
    var myConstructor = CreateConstructor(typeof(MyClass), typeof(int), typeof(string));

    Stopwatch sw = Stopwatch.StartNew();

    for (int i = 0; i < 1024 * 1024 * 10; i++)
    {
        var myObject = myConstructor(10, "test message");
    }

    sw.Stop();
    Trace.WriteLine("Expression: " + sw.Elapsed);
}

TestActivator();
TestReflection();
TestExpression();

Sonuçlar:

Activator: 00:00:13.8210732
Reflection: 00:00:05.2986945
Expression: 00:00:00.6681696

Kullanımı Expressionsise +/- 8 kat daha hızlı çağırma daha ConstructorInfove +/- 20 kat daha hızlı kullanmaktan dahaActivator


MyClass <T> 'ı yapıcı genel MyClass (T verileri) ile oluşturmak istiyorsanız ne yapacağınız konusunda herhangi bir fikriniz var mı? Bu durumda, Expression.Convert bir istisna atar ve dönüştürmek için genel kısıtlama taban sınıfını kullanırsam, o zaman Expression.New atar çünkü yapıcı bilgisi genel bir tür içindir
Mason

@Mason (;-) yanıtlaması biraz zaman aldı) var myConstructor = CreateConstructor(typeof(MyClass<int>), typeof(int));bu iyi çalışıyor. Bilmiyorum.
Jeroen van Langen

19

Bu sizin durumunuzda çalışmaz. Yalnızca boş bir kurucuya sahip olduğu kısıtlamayı belirtebilirsiniz:

public static string GetAllItems<T>(...) where T: new()

Yapabileceğiniz şey, bu arayüzü tanımlayarak özellik enjeksiyonunu kullanmaktır:

public interface ITakesAListItem
{
   ListItem Item { set; }
}

Daha sonra yönteminizi şu şekilde değiştirebilirsiniz:

public static string GetAllItems<T>(...) where T : ITakesAListItem, new()
{
   ...
   List<T> tabListItems = new List<T>();
   foreach (ListItem listItem in listCollection) 
   {
       tabListItems.Add(new T() { Item = listItem });
   } 
   ...
}

Diğer alternatif, FuncJaredPar tarafından tarif edilen yöntemdir.


bu yapıcıda argümanları alan herhangi bir mantığı atlayacaktı, değil mi?
Jared'in

3
Sağda, bu, T () varsayılan kurucusunun mantığını çağırır ve daha sonra "Item" özelliğini ayarlar. Varsayılan olmayan bir kurucu mantığını çağırmaya çalışıyorsanız, bu size yardımcı olmaz.
Scott Stafford

7

Derleyiciye T'nin varsayılan bir kurucu sağlaması garanti edildiğini bildirmek için T: new () parametresini nereye eklemeniz gerekir.

public static string GetAllItems<T>(...) where T: new()

1
Update: doğru hata iletisi: 'T' bir değişken örneği oluştururken argümanlar sağlayamaz
kilodan

Çünkü boş bir kurucu kullanmıyorsunuz, nesneye bir argüman aktarıyorsunuz. Genel Type'ın yeni (nesne) bir parametreye sahip olduğunu belirtmeden bunu kaldıramaz.
Min

Daha sonra şunlardan birini yapmanız gerekir: 1. Yansımayı kullan 2. Parametreyi yapıcı yerine bir başlatma yöntemine geçirin; burada başlatma yöntemi, türünüzün uyguladığı ve burada T: beyanı. Seçenek 1, kodunuzun geri kalanı için en düşük etkidir, ancak seçenek 2 derleme zamanı denetimi sağlar.
Richard

Yansımayı kullanma! Aynı etkiyi elde etmenizi sağlayacak diğer cevaplarda belirtildiği gibi başka yollar da vardır.
Garry Shutler

@Garry - Yansımanın mutlaka en iyi yaklaşım olmadığını kabul ediyorum, ancak kod tabanının geri kalanında minimum değişiklikle gerekli olanı elde etmenize izin veriyor. Bununla birlikte, @JaredPar'dan fabrika delege yaklaşımını çok tercih ediyorum.
Richard

7

Bir üye alanını veya özelliğini yapıcı parametresiyle başlatmak istiyorsanız, C #> = 3'te çok daha kolay yapabilirsiniz:

public static string GetAllItems<T>(...) where T : InterfaceOrBaseClass, new() 
{ 
   ... 
   List<T> tabListItems = new List<T>(); 
   foreach (ListItem listItem in listCollection)  
   { 
       tabListItems.Add(new T{ BaseMemberItem = listItem }); // No error, BaseMemberItem owns to InterfaceOrBaseClass. 
   }  
   ... 
} 

Garry Shutler'ın söylediği şey bu, ama ek bir not vermek istiyorum.

Tabii ki, bir alan değeri ayarlamaktan daha fazla şey yapmak için bir özellik numarası kullanabilirsiniz. Bir "set ()" özelliği, ilgili alanlarını ayarlamak için gereken herhangi bir işlemi ve nesnenin kendisi için başka herhangi bir ihtiyacı tetikleyebilir; bu, nesne kullanılmadan önce tam bir başlatmanın gerçekleşip gerçekleşmeyeceğini görmek için bir kontrol de dahil olmak üzere tam bir yapı ( evet, bu çirkin bir çözüm, ancak M $ 'ın yeni () sınırlamasının üstesinden geliyor.

Planlanmış bir delik mi yoksa kazara bir yan etki mi olduğundan emin olamıyorum, ama işe yarıyor.

MS insanlarının dile nasıl yeni özellikler ekledikleri ve tam bir yan etki analizi yapmadıkları çok komik. Tüm jenerik şey bunun iyi bir kanıtıdır ...


1
Her iki kısıtlamaya da ihtiyaç vardır. InterfaceOrBaseClass derleyiciyi BaseMemberItem alanından / özelliğinden haberdar eder. "New ()" kısıtlaması yorumlanırsa hatayı tetikler: Hata 6 Yeni () kısıtlamasına sahip olmadığından 'T' değişken türünün bir örneği oluşturulamaz
fljx

Karşılaştığım bir durum tam olarak burada sorulan soru gibi değildi, ancak bu cevap beni gitmem gereken yere getirdi ve çok iyi çalışıyor gibi görünüyor.
RubyHaus

5
Birisi Microsoft'tan "M $" olarak bahsettiğinde, ruhumun küçük bir kısmı acı çekiyor.
Mathias Lykkegaard Lorenzen

6

Bunu yapmak için gerekli bir hata alıyorum "T türü parametre T örneğini oluştururken bağımsız değişkenler sağlayamaz" buldum:

var x = Activator.CreateInstance(typeof(T), args) as T;

5

Kullanacağınız sınıfa erişiminiz varsa, kullandığım yaklaşımı kullanabilirsiniz.

Alternatif bir içerik oluşturucuya sahip bir arayüz oluşturun:

public interface ICreatable1Param
{
    void PopulateInstance(object Param);
}

Sınıflarınızı boş bir içerik oluşturucu ile yapın ve bu yöntemi uygulayın:

public class MyClass : ICreatable1Param
{
    public MyClass() { //do something or nothing }
    public void PopulateInstance (object Param)
    {
        //populate the class here
    }
}

Şimdi genel yöntemlerinizi kullanın:

public void MyMethod<T>(...) where T : ICreatable1Param, new()
{
    //do stuff
    T newT = new T();
    T.PopulateInstance(Param);
}

Erişiminiz yoksa, hedef sınıfı kaydırın:

public class MyClass : ICreatable1Param
{
    public WrappedClass WrappedInstance {get; private set; }
    public MyClass() { //do something or nothing }
    public void PopulateInstance (object Param)
    {
        WrappedInstance = new WrappedClass(Param);
    }
}

0

Bu bir tür mucky ve bir çeşit mucky dediğimde isyan etmek anlamına gelebilir, ancak varsayalım ki parametreli tipinizi boş bir kurucu ile verebilirsiniz, o zaman:

public static T GetTInstance<T>() where T: new()
{
    var constructorTypeSignature = new Type[] {typeof (object)};
    var constructorParameters = new object[] {"Create a T"};
    return (T) new T().GetType().GetConstructor(constructorTypeSignature).Invoke(constructorParameters);
}

Parametreli bir türden bir argümanla bir nesne oluşturmanıza etkili bir şekilde izin verir. Bu durumda istediğim yapıcı türünün tek bir argümanı olduğunu varsayıyorum object. İzin verilen boş yapıcıyı kullanarak kukla bir T örneği oluştururuz ve daha sonra diğer kurucularından birini almak için yansıma kullanırız.


0

Bazen özellik enjeksiyonu kullanarak cevaplara benzeyen ancak kodu daha temiz tutan bir yaklaşım kullanıyorum. Bir dizi özellik içeren bir temel sınıf / arabirime sahip olmak yerine, yalnızca bir "sanal adamın yapıcısı" görevi gören bir (sanal) Initialize () - yöntemi içerir. Daha sonra her sınıfın, bir yapıcı gibi kendi başlatmasını işlemesine izin verebilirsiniz, bu da miras zincirlerini ele almak için uygun bir yol ekler.

Sık sık kendimi zincirdeki her sınıfın benzersiz özelliklerini başlatmasını istediğim durumlarda bulur ve daha sonra üst öğenin benzersiz özelliklerini ve benzerlerini başlatan üst öğesinin Initialize () yöntemini çağırır. Bu, özellikle farklı sınıflara sahipken, ancak benzer bir hiyerarşiye sahipse, örneğin DTO: s ile / 'den eşlenen iş nesneleri için kullanışlıdır.

Başlatma için ortak bir Sözlük kullanan örnek:

void Main()
{
    var values = new Dictionary<string, int> { { "BaseValue", 1 }, { "DerivedValue", 2 } };

    Console.WriteLine(CreateObject<Base>(values).ToString());

    Console.WriteLine(CreateObject<Derived>(values).ToString());
}

public T CreateObject<T>(IDictionary<string, int> values)
    where T : Base, new()
{
    var obj = new T();
    obj.Initialize(values);
    return obj;
}

public class Base
{
    public int BaseValue { get; set; }

    public virtual void Initialize(IDictionary<string, int> values)
    {
        BaseValue = values["BaseValue"];
    }

    public override string ToString()
    {
        return "BaseValue = " + BaseValue;
    }
}

public class Derived : Base
{
    public int DerivedValue { get; set; }

    public override void Initialize(IDictionary<string, int> values)
    {
        base.Initialize(values);
        DerivedValue = values["DerivedValue"];
    }

    public override string ToString()
    {       
        return base.ToString() + ", DerivedValue = " + DerivedValue;
    }
}

0

İhtiyacınız olan tek şey ListItem'den T tipinize dönüştürme yapmaksa, bu dönüştürmeyi T sınıfında dönüştürme operatörü olarak uygulayabilirsiniz.

public class T
{
    public static implicit operator T(ListItem listItem) => /* ... */;
}

public static string GetAllItems(...)
{
    ...
    List<T> tabListItems = new List<T>();
    foreach (ListItem listItem in listCollection) 
    {
        tabListItems.Add(listItem);
    } 
    ...
}

-4

Sadece yeni bir kurucuya sahip nesnelere izin vermek için T'yi bir where ifadesiyle sınırlamanız gerektiğine inanıyorum.

Şu anda nesnesiz nesneler dahil her şeyi kabul ediyor.


1
Bu cevabı değiştirmek isteyebilirsiniz, çünkü cevapladıktan sonra bu soru bağlam dışı bırakılmıştır.
shuttle87
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.