web-dev-qa-db-ja.com

ジェネリック型のインスタンスを作成しますか?

BaseFruitint weightを受け入れるコンストラクターがある場合、このような汎用メソッドでフルーツのインスタンスを作成できますか?

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

コメントの後ろに例が追加されています。 BaseFruitにパラメーターなしのコンストラクターを指定し、メンバー変数を使用してすべてを入力する場合にのみ、これを実行できるようです。私の実際のコード(フルーツに関するものではありません)では、これはかなり非現実的です。

-Update-
だから、どういうわけか制約によって解決できないようです。回答から、3つの候補ソリューションがあります。

  • 工場パターン
  • 反射
  • アクティベーター

リフレクションは最もクリーンでないと思う傾向がありますが、他の2つを決定することはできません。

199
Boris Callens

さらに簡単な例:

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

Tでnew()制約を使用するのは、コンパイラがコンパイル時にパブリックパラメータレスコンストラクターをチェックするためだけであり、型の作成に使用される実際のコードはActivatorクラスであることに注意してください。

既存の特定のコンストラクターについて確認する必要がありますが、この種の要件はコードの匂いかもしれません(または、c#の現在のバージョンでは避けるべきものです)。

292
meandmycode

パラメーター化されたコンストラクターは使用できません。 「where T : new()」制約がある場合は、パラメーターなしのコンストラクターを使用できます。

それは痛みですが、それは人生です:(

これは "static interfaces" で対処したいことの1つです。その後、静的メソッド、演算子、およびコンストラクターを含めるようにTを制約し、それらを呼び出すことができます。

84
Jon Skeet

はい;場所を変更します。

where T:BaseFruit, new()

ただし、これはparameterlessコンストラクターでのみ機能します。プロパティを設定する他の方法(プロパティ自体または同様の設定)が必要です。

49
Adam Robinson

最も簡単なソリューションActivator.CreateInstance<T>()

22
user1471935

Jonが指摘したように、これは非パラメーターレスコンストラクターを制約するための人生です。ただし、別の解決策はファクトリパターンを使用することです。これは簡単に制約されます

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

さらに別のオプションは、機能的なアプローチを使用することです。ファクトリメソッドを渡します。

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

リフレクションを使用して行うことができます。

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

編集: コンストラクター== nullチェックを追加しました。

編集: キャッシュを使用した高速バリアント:

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

このメソッドを作成しました:

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

私はそれをこのように使用します:

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

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

コード:

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

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

User1471935の提案への追加として:

1つ以上のパラメーターを持つコンストラクターを使用して汎用クラスをインスタンス化するために、Activatorクラスを使用できるようになりました。

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

オブジェクトのリストは、提供するパラメーターです。 Microsoftによると

CreateInstance [...]は、指定されたパラメーターに最も一致するコンストラクターを使用して、指定されたタイプのインスタンスを作成します。

また、CreateInstanceの汎用バージョン(CreateInstance<T>())もありますが、コンストラクターパラメーターを指定することもできません。

0
Rob Vermeulen

最近、非常によく似た問題に出会いました。ソリューションを皆さんと共有したかっただけです。を使用して、jsonオブジェクトからCar<CarA>のインスタンスを作成したかった。

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