Yapıcısı bir parametre gerektiren genel tip örneği mi oluşturuyor?


230

Kabul BaseFruiteden bir kurucu varsa , int weightböyle bir jenerik yöntemle bir parça meyve başlatabilir miyim?

public void AddFruit<T>()where T: BaseFruit{
    BaseFruit fruit = new T(weight); /*new Apple(150);*/
    fruit.Enlist(fruitManager);
}

Yorumların arkasına bir örnek eklenmiştir. Bunu sadece BaseFruitparametresiz bir kurucu verir ve sonra üye değişkenler aracılığıyla her şeyi doldurursam yapabilirim. Gerçek kodumda (meyve ile ilgili değil) bu oldukça pratik değildir.

-Güncelleme-
Öyle görünüyor ki, o zaman hiçbir şekilde kısıtlamalar ile çözülemez. Cevaplardan üç aday çözüm var:

  • Fabrika Desen
  • yansıma
  • aktivatör

Yansımanın en az temiz olduğunu düşünmeye eğilimliyim, ancak diğer ikisi arasında karar veremiyorum.


1
BTW: Bugün bunu muhtemelen IoC'nin tercih ettiği kütüphane ile çözerdim.
Boris Callens

Yansıma ve Aktivatör aslında yakından ilişkilidir.
Rob Vermeulen

Yanıtlar:


335

Ayrıca daha basit bir örnek:

return (T)Activator.CreateInstance(typeof(T), new object[] { weight });

T üzerinde new () kısıtlamasını kullanmanın, derleyiciyi derleme zamanında bir genel parametresiz yapıcı için kontrol etmesini sağlamak olduğunu unutmayın, türü oluşturmak için kullanılan gerçek kod Activator sınıfıdır.

Mevcut belirli kurucu ile ilgili olarak kendinizi güvence altına almanız gerekir ve bu tür bir gereksinim bir kod kokusu olabilir (ya da c # 'daki mevcut sürümde kaçınmaya çalışmanız gereken bir şey olabilir).


Bu kurucu taban sınıfında (BaseFruit) olduğundan bir kurucuya sahip olacağını biliyorum. Ama gerçekten, bir gün basefruit'in daha fazla parametreye ihtiyacı olduğuna karar verirsem, vidalanabilirim. Yine de ACtivator sınıfına bakacağız. Daha önce duymadım.
Boris Callens

3
Bu iyi çalıştı. Ayrıca bir CreateInstance <T> () prosedürü vardır, ancak bazı rason parametreleri için aşırı yüklenme yoktur ..
Boris Callens

20
Kullanmaya gerek yoktur new object[] { weight }. CreateInstanceparams ile bildirilir public static object CreateInstance(Type type, params object[] args), böylece yapabilirsiniz return (T) Activator.CreateInstance(typeof(T), weight);. Birden fazla parametre varsa, bunları ayrı bağımsız değişkenler olarak iletin. Sadece zaten yapılandırılmış bir dizi parametreye sahipseniz, onu dönüştürmek object[]ve aktarmak için uğraşmalısınız CreateInstance.
ErikE

2
Bunun okuduğum performans sorunları olacak. Bunun yerine derlenmiş bir lambda kullanın. vagifabilov.wordpress.com/2010/04/02/…
David

1
@RobVermeulen - Her Fruit sınıfında, Funcyeni örneği oluşturan bir içeren statik bir özellik gibi bir şey düşünüyorum . AppleYapıcı kullanımı olduğunu varsayalım new Apple(wgt). Sonra eklemek Applesınıfın bu tanımı: static Func<float, Fruit> CreateOne { get; } = (wgt) => new Apple(wgt);In Fabrika tanımlamak public static Fruit CreateFruitGiven(float weight, Func<float, Fruit> createOne) { return createOne(weight); } Kullanımı: Factory.CreateFruit(57.3f, Apple.CreateOne);- oluşturur ve bir döndüren Appleile weight=57.3f.
ToolmakerSteve

92

Parametreli herhangi bir kurucu kullanamazsınız. " where T : new()" Kısıtlamanız varsa parametresiz bir kurucu kullanabilirsiniz .

Bu bir acı, ama hayat böyle :(

Bu "statik arayüzler" ile ele almak istediğim şeylerden biri . Daha sonra, statik yöntemleri, operatörleri ve yapıcıları dahil etmek için T'yi kısıtlayabilir ve sonra bunları çağırabilirsiniz.


2
En azından böyle kısıtlamalar yapabilirsiniz - Java her zaman beni hayal kırıklığına uğratır.
Marcel Jackwerth

@JonSkeet: Ben API jenerik VB6.0 çağrılmak üzere genel ile maruz ... hala işe yarıyor mu?
Roy Lee

@Roylee: Hiçbir fikrim yok, ama şüphelenmiyorum.
Jon Skeet

Statik arayüzlerin bir dil derleyicisi tarafından çalışma süresinde değişiklik yapılmadan eklenebileceğini düşünürdüm, ancak dil takımlarının ayrıntılar üzerinde koordinasyonu iyi olurdu. Statik arabirim uyguladığını iddia eden her sınıfın, kendi türünün statik tekil örneğini tanımlayan, arabirimle ilgili belirli bir ada sahip iç içe bir sınıf içermesi gerektiğini belirtin. Arayüz ile ilişkili, Yansıma ile bir kez singletona yüklenmesi gereken, ancak bundan sonra doğrudan kullanılabilen bir örnek alanı olan statik bir genel tip olacaktır.
supercat

Parametreli bir yapıcı kısıtlaması aynı şekilde ele alınabilir (bir fabrika yöntemi ve dönüş türü için genel bir parametre kullanılarak); her iki durumda da, böyle bir özelliği desteklemeyen bir dilde yazılan kodun, uygun statik türü tanımlamadan arabirimi uyguladığını iddia etmesini engelleyemez, bu nedenle bu tür diller kullanılarak yazılan kod çalışma zamanında başarısız olabilir, ancak kullanıcıda Yansıma önlenebilir kodu.
supercat

61

Evet; nerede olduğunuzu değiştirin:

where T:BaseFruit, new()

Ancak, bu yalnızca parametresiz kurucularla çalışır . Mülkünüzü ayarlamak için başka bazı araçlara sahip olmanız gerekir (mülkün kendisini veya benzer bir şeyi ayarlamak).


Eğer kurucu parametreleri yoksa bu benim için güvenli görünüyor.
PerpetualStudent

Hayatımı kurtardın. T'yi sınıf ve yeni () anahtar kelimeyle sınırlamayı başaramadım.
Genotypek

28

En basit çözüm Activator.CreateInstance<T>()


1
Öneri için teşekkürler, olması gereken yere geldi. Bu, parametrelenmiş bir kurucu kullanmanıza izin vermese de. Ancak, genel olmayan varyantı kullanabilirsiniz: Activator.CreateInstance (typeof (T), yeni nesne [] {...}); burada nesne dizisi yapıcı için bağımsız değişkenler içerir.
Rob Vermeulen

19

Jon'un işaret ettiği gibi, bu parametresiz bir kurucuyu sınırlamak için hayattır. Ancak farklı bir çözüm, fabrika düzeni kullanmaktır. Bu kolayca kısıtlanabilir

interface IFruitFactory<T> where T : BaseFruit {
  T Create(int weight);
}

public void AddFruit<T>( IFruitFactory<T> factory ) where T: BaseFruit {    
  BaseFruit fruit = factory.Create(weight); /*new Apple(150);*/    
  fruit.Enlist(fruitManager);
}

Yine bir başka seçenek de fonksiyonel bir yaklaşım kullanmaktır. Fabrika yöntemiyle geçin.

public void AddFruit<T>(Func<int,T> factoryDel) where T : BaseFruit { 
  BaseFruit fruit = factoryDel(weight); /* new Apple(150); */
  fruit.Enlist(fruitManager);
}

2
İyi bir öneri - dikkatli olmamanıza rağmen, Java DOM API'sinin cehennemine girebilirsin, fabrikalar bolca :(
Jon Skeet

Evet, bu kendimi düşündüğüm bir çözüm. Ama kısıtlamalar çizgisinde bir şey umuyordum. Sanırım değil o zaman ..
Boris Callens

@boris, ne yazık ki aradığınız kısıtlama dili şu anda mevcut değil
JaredPar

11

Yansımayı kullanarak yapabilirsiniz:

public void AddFruit<T>()where T: BaseFruit
{
  ConstructorInfo constructor = typeof(T).GetConstructor(new Type[] { typeof(int) });
  if (constructor == null)
  {
    throw new InvalidOperationException("Type " + typeof(T).Name + " does not contain an appropriate constructor");
  }
  BaseFruit fruit = constructor.Invoke(new object[] { (int)150 }) as BaseFruit;
  fruit.Enlist(fruitManager);
}

EDIT: Eklendi yapıcı == boş denetim.

EDIT: Önbellek kullanan daha hızlı bir varyant:

public void AddFruit<T>()where T: BaseFruit
{
  var constructor = FruitCompany<T>.constructor;
  if (constructor == null)
  {
    throw new InvalidOperationException("Type " + typeof(T).Name + " does not contain an appropriate constructor");
  }
  var fruit = constructor.Invoke(new object[] { (int)150 }) as BaseFruit;
  fruit.Enlist(fruitManager);
}
private static class FruitCompany<T>
{
  public static readonly ConstructorInfo constructor = typeof(T).GetConstructor(new Type[] { typeof(int) });
}

Diğerlerinin açıkladığı gibi, yansımanın yükünü beğenmeme rağmen, şu anda olduğu gibi. Bu kurucuya nasıl çok fazla çağrılmayacağını görünce, bununla gidebilirim. Veya fabrika. Henüz bilmiyorum.
Boris Callens

Bu şu anda benim tercih edilen yaklaşımım çünkü çağırma tarafında daha fazla karmaşıklık katmıyor.
Rob Vermeulen

Ama şimdi yukarıdaki yansıma çözümü ile benzer nastiness sahip Aktivatör önerisi hakkında okudum, ama daha az kod satırı ile :) Aktivatör seçeneği için gidiyorum.
Rob Vermeulen

1

User1471935'in önerisine ek olarak:

Bir veya daha fazla parametreye sahip bir kurucu kullanarak genel bir sınıfı başlatmak için artık Activator sınıfını kullanabilirsiniz.

T instance = Activator.CreateInstance(typeof(T), new object[] {...}) 

Nesne listesi sağlamak istediğiniz parametrelerdir. Microsoft'a göre :

CreateInstance [...], belirtilen parametrelerle en iyi eşleşen yapıcıyı kullanarak belirtilen türde bir örnek oluşturur.

Ayrıca CreateInstance ( CreateInstance<T>()) ' ın genel bir sürümü vardır, ancak bu da yapıcı parametreleri sağlamanıza izin vermez.


1

Bu yöntemi oluşturdum:

public static V ConvertParentObjToChildObj<T,V> (T obj) where V : new()
{
    Type typeT = typeof(T);
    PropertyInfo[] propertiesT = typeT.GetProperties();
    V newV = new V();
    foreach (var propT in propertiesT)
    {
        var nomePropT = propT.Name;
        var valuePropT = propT.GetValue(obj, null);

        Type typeV = typeof(V);
        PropertyInfo[] propertiesV = typeV.GetProperties();
        foreach (var propV in propertiesV)
        {
            var nomePropV = propV.Name;
            if(nomePropT == nomePropV)
            {
                propV.SetValue(newV, valuePropT);
                break;
            }
        }
    }
    return newV;
}

Bunu şu şekilde kullanıyorum:

public class A 
{
    public int PROP1 {get; set;}
}

public class B : A
{
    public int PROP2 {get; set;}
}

Kod:

A instanceA = new A();
instanceA.PROP1 = 1;

B instanceB = new B();
instanceB = ConvertParentObjToChildObj<A,B>(instanceA);

0

Son zamanlarda çok benzer bir sorunla karşılaştım. Çözümümüzü hepinizle paylaşmak istedim. Ben Car<CarA>bir enum vardı kullanarak bir json nesneden bir örneği oluşturmak istedi :

Dictionary<MyEnum, Type> mapper = new Dictionary<MyEnum, Type>();

mapper.Add(1, typeof(CarA));
mapper.Add(2, typeof(BarB)); 

public class Car<T> where T : class
{       
    public T Detail { get; set; }
    public Car(T data)
    {
       Detail = data;
    }
}
public class CarA
{  
    public int PropA { get; set; }
    public CarA(){}
}
public class CarB
{
    public int PropB { get; set; }
    public CarB(){}
}

var jsonObj = {"Type":"1","PropA":"10"}
MyEnum t = GetTypeOfCar(jsonObj);
Type objectT = mapper[t]
Type genericType = typeof(Car<>);
Type carTypeWithGenerics = genericType.MakeGenericType(objectT);
Activator.CreateInstance(carTypeWithGenerics , new Object[] { JsonConvert.DeserializeObject(jsonObj, objectT) });

-2

Yüksek performansla aşağıdakileri yaparak hala mümkündür:

    //
    public List<R> GetAllItems<R>() where R : IBaseRO, new() {
        var list = new List<R>();
        using ( var wl = new ReaderLock<T>( this ) ) {
            foreach ( var bo in this.items ) {
                T t = bo.Value.Data as T;
                R r = new R();
                r.Initialize( t );
                list.Add( r );
            }
        }
        return list;
    }

ve

    //
///<summary>Base class for read-only objects</summary>
public partial interface IBaseRO  {
    void Initialize( IDTO dto );
    void Initialize( object value );
}

İlgili sınıflar daha sonra bu arayüzden türetilmeli ve buna göre başlatılmalıdır. Benim durumumda, bu kodun genel parametre olarak zaten <T> olan çevreleyen bir sınıfın parçası olduğunu lütfen unutmayın. R, benim durumumda, aynı zamanda salt okunur bir sınıftır. IMO, Initialize () işlevlerinin genel kullanılabilirliğinin değişmezlik üzerinde olumsuz bir etkisi yoktur. Bu sınıfın kullanıcısı başka bir nesneyi yerleştirebilir, ancak bu temeldeki koleksiyonu değiştirmez.

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.