コンストラクターにパラメーターが必要なジェネリック型のインスタンスを作成しますか?


230

BaseFruitを受け入れるコンストラクターがある場合、int weightこのようなジェネリックメソッドで果物の一部をインスタンス化できますか?

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

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

-更新-
だから、それはそれではどのような方法でも制約によって解決できないようです。回答から3つの候補ソリューションがあります。

  • 工場パターン
  • 反射
  • 活性剤

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


1
ところで:今日、私はおそらくこれを選択したIoCライブラリで解決するでしょう。
Boris Callens 2017年

ReflectionとActivatorは実際には密接に関連しています。
Rob Vermeulen

回答:


335

さらに簡単な例:

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

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

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


このコンストラクターはベースクラス(BaseFruit)上にあるので、コンストラクターを持つことになります。しかし、確かに、ある日basefruitにさらに多くのパラメータが必要だと判断した場合、私は失敗する可能性があります。ACtivatorクラスを調べます。今まで聞いたことがありませんでした。
Boris Callens

3
これはうまくいきました。CreateInstance <T>()プロシージャもありますが、これには、いくつかのrasonのパラメーターのオーバーロードがありません。–
Boris Callens

20
使用する必要はありませんnew object[] { weight }CreateInstanceはparamsを使用して宣言されているpublic static object CreateInstance(Type type, params object[] args)ため、だけを実行できますreturn (T) Activator.CreateInstance(typeof(T), weight);。複数のパラメータがある場合は、個別の引数として渡します。パラメータの構築可能な列挙型がすでにある場合にのみ、それを変換してobject[]に渡す必要がありCreateInstanceます。
ErikE 2015

2
これには、私が読んだパフォーマンスの問題があります。代わりにコンパイル済みラムダを使用してください。vagifabilov.wordpress.com/2010/04/02/…–
デビッド

1
@RobVermeulen- Func新しいインスタンスを作成するを含む、各Fruitクラスの静的プロパティのようなものだと思います。仮定Appleコンストラクタの使用量がありますnew Apple(wgt)。その後に追加Appleクラスこの定義を:static Func<float, Fruit> CreateOne { get; } = (wgt) => new Apple(wgt);の工場は、定義public static Fruit CreateFruitGiven(float weight, Func<float, Fruit> createOne) { return createOne(weight); } 使用法: Factory.CreateFruit(57.3f, Apple.CreateOne);-作成して返すApple、とweight=57.3f
ToolmakerSteve

92

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

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

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


2
少なくともあなたはそのような制約をすることができます-Javaはいつも私を失望させます。
Marcel Jackwerth 2009

@JonSkeet:VB6.0で呼び出される.NETジェネリックでAPIを公開した場合、それはまだ機能しますか?
ロイ・リー

@Roylee:わからないが、そうではないようだ。
Jon Skeet

言語チームに詳細を調整してもらうのは良いことですが、静的インターフェイスはランタイムを変更せずに言語コンパイラによって追加できると思います。静的インターフェイスの実装を要求するすべてのクラスに、独自のタイプの静的シングルトンインスタンスを定義する特定のインターフェイス関連の名前を持つネストされたクラスが含まれている必要があることを指定します。インターフェイスに関連付けられるのは、インスタンスフィールドを持つ静的なジェネリック型であり、Reflectionを介してシングルトンを1回ロードする必要がありますが、その直後に使用できます。
スーパーキャット2014年

パラメーター化されたコンストラクター制約は、ほぼ同じ方法で処理できます(ファクトリーメソッドと、戻り値の型の汎用パラメーターを使用)。どちらの場合も、そのような機能をサポートしない言語で記述されたコードが適切な静的型を定義せずにインターフェイスを実装するように要求することを妨げないため、そのような言語を使用して記述されたコードは実行時に失敗する可能性がありますが、ユーザーではリフレクションを回避できますコード。
スーパーキャット2014年

61

はい; あなたの場所を変更してください:

where T:BaseFruit, new()

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


コンストラクタにパラメータがない場合、これは私には安全に思えます。
PerpetualStudent

あなたは私の命を救った。Tをクラスとnew()キーワードに制限することができませんでした。
Genotypek

28

最も簡単な解決策 Activator.CreateInstance<T>()


1
提案をありがとう、私は必要な場所に行きました。ただし、パラメーター化されたコンストラクターを使用することはできません。ただし、ジェネリックではないバリアントを使用することもできます。Activator.CreateInstance(typeof(T)、new object [] {...})オブジェクト配列にはコンストラクタの引数が含まれます。
Rob Vermeulen

19

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

2
良い提案-注意しないと、Java DOM APIの地獄に陥る可能性がありますが、工場は豊富です:(
Jon Skeet

はい、これは私が考えていた解決策です。しかし、私は制約の線に何かを望んでいました。その後は推測しないでください..
ボリスカレンス

@boris、残念ながら、探している制約言語は現時点では存在しません
JaredPar

11

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

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

他の人が説明したように、私は反射のオーバーヘッドが好きではありませんが、これは現在の方法です。このコンストラクターがあまり呼び出されない方法を見て、これでうまくいくでしょう。または工場。まだわかりません。
Boris Callens

これは、呼び出し側の複雑さを増すことはないため、現在のところ私が好むアプローチです。
Rob Vermeulen

しかし、今、私はアクティベーターの提案について読みました。これは、上記のリフレクションソリューションと似たような素朴さを持っていますが、コードの行数は少なくなっています:)アクティベーターオプションに行きます。
Rob Vermeulen

1

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

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

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

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

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

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


1

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

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

0

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

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

次のことを実行することで、高いパフォーマンスを維持できます。

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

そして

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

次に、関連するクラスはこのインターフェースから派生し、それに応じて初期化する必要があります。私の場合、このコードは周囲のクラスの一部であり、ジェネリックパラメーターとして既に<T>を持っていることに注意してください。私の場合、Rも読み取り専用クラスです。IMO、Initialize()関数のパブリックアベイラビリティは、不変性に悪影響を及ぼしません。このクラスのユーザーは別のオブジェクトを配置できますが、これにより、基になるコレクションは変更されません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.