テンプレート型のC#ジェネリックnew()に引数を渡す


409

リストに追加するときに、コンストラクターを介してT型の新しいオブジェクトを作成しようとしています。

コンパイルエラーが発生する:エラーメッセージは次のとおりです。

'T':変数のインスタンスを作成するときに引数を提供できません

しかし、私のクラスにはコンストラクター引数があります!どうすればこれを機能させることができますか?

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
この機能を言語に取り入れる
Ian Kemp

Microsoftのドキュメントで、コンパイラエラーCS0417を参照してください。
DavidRR 2017年

1
この機能を言語に取り入れるための提案は、github.com
アクティビティの削減

回答:


410

関数でジェネリック型のインスタンスを作成するには、「新しい」フラグでそれを制約する必要があります。

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

ただし、これは、パラメーターを持たないコンストラクターを呼び出す場合にのみ機能します。ここではそうではありません。代わりに、パラメーターに基づくオブジェクトの作成を可能にする別のパラメーターを提供する必要があります。最も簡単なのは関数です。

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

その後、そのように呼び出すことができます

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

ジェネリッククラスから内部的に呼び出された場合、これはどのように機能しますか?以下の回答にコードを投稿しました。具体的なクラスはジェネリッククラスであるため、内部的にはわかりません。これを回避する方法はありますか。私は、コンストラクタを持っているロジックバイパスその意志のような構文初期化剤使用して、プロパティの他の提案を使用したくない
ChrisCa

別の質問に私のコードを追加stackoverflow.com/questions/1682310/...
ChrisCa

21
これは現在、C#の最も厄介な制限の1つです。クラスを不変にしたいと思います。プライベートセッターだけを使用すると、副作用によってクラスが無効な状態になることが不可能になります。私はそのFuncとラムダを使用することも好きですが、一般にプログラマーはまだラムダを知らないため、クラスを理解することが難しくなるため、ビジネスの世界では依然として問題であることはわかっています。
Tuomas Hietanen 09

1
ありがとう。私の場合、メソッドを呼び出すときにコンストラクターの引数がわかっているので、パラメーターで構成できないというTypeパラメーターの制限を回避する必要があるだけなので、thunkを使用しました。サンクはメソッドへのオプションのパラメーターであり、提供された場合にのみ使用します。T result = thunk == null ? new T() : thunk(); これの利点は、メソッドの内部と外部にT作成するのではなく、作成ロジックを1か所に統合するTことです。
カールG

これらはC#言語がプログラマーにノーと言い、常にイエスと言うのをやめることに決めた場所の1つだと思います!このアプローチはオブジェクトを作成する少し厄介な方法ですが、今はそれを使用する必要があります。
AmirHossein Rezaei

331

.Net 3.5以降では、アクティベータークラスを使用できます。

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

1
式ツリーを使用してオブジェクトを構築することもできます
Welly Tambunan 2013年

4
argsとは何ですか?オブジェクト[]?
ロドニーP.バルバティ2013年

3
はい、argsはTのコンストラクターに提供される値を指定するobject []です: "new object [] {par1、par2}"
TechNyquist


3
警告:このために専用のコンストラクターがある場合Activator.CreateInstance、コンストラクターはまったく使用されていないように見え、誰かが「クリーンアップ」して削除しようとする可能性があります(ランタイムエラーが発生するため)将来的にランダムな時間)。このコンストラクタを使用する場所にダミー関数を追加して、削除しようとするとコンパイルエラーが発生するようにすることを検討してください。
jrh

51

誰も「リフレクション」の回答を投稿する気にならなかったので(個人的にはこれが最良の回答だと思います)、次のようになります。

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

編集:この回答は、.NET 3.5のActivator.CreateInstanceのため非推奨ですが、古い.NETバージョンでも引き続き役立ちます。


私の理解では、パフォーマンスへの影響のほとんどは、最初にConstructorInfoを取得することです。それをプロファイリングせずに私の言葉をそれに取ってはいけません。その場合は、ConstructorInfoを格納して後で再利用するだけで、リフレクションを介してインスタンス化を繰り返すことによるパフォーマンスへの影響を軽減できます。
Kelsie

19
コンパイル時のチェックが不足していることは、より懸念の原因になると思います。
Dave Van den Eynde

1
@James同意します。これを「答え」と見なさないことに驚きました。実際、私はこの質問を検索しました。反射を行ってから長い時間が経過しているため、(あなたのような)簡単な例が見つかると期待しています。とにかく、私から+1しますが、アクティベーターの+1も答えます。私はActivatorが何をしているかを調べたところ、何が行われているかは、非常によく設計されたリフレクションであることがわかりました。:)
Mike

GetConstructor()呼び出しは負荷が高いため、ループの前にキャッシュする価値があります。このように、ループ内でInvoke()のみを呼び出すことで、両方を呼び出すよりも、またはActivator.CreateInstance()を使用するよりもはるかに高速です。
Cosmin Rus

30

オブジェクト初期化子

パラメーターを持つコンストラクターがプロパティの設定以外に何も行わない場合は、コンストラクターを呼び出すのではなく、オブジェクト初期化子を使用してC#3以降でこれを行うことができます(これは、前述のように不可能です)。

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

これを使用すると、デフォルトの(空の)コンストラクターにコンストラクターロジックをいつでも配置できます。

Activator.CreateInstance()

または、次のようにActivator.CreateInstance()を呼び出すこともできます。

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には、実行速度が最優先であり、別のオプションを維持できる場合は、回避したいパフォーマンスオーバーヘッドがいくつかある可能性があることに注意してください。


これはT、その不変式を保護することを防ぎます(T0を超える依存関係または必要な値がある場合T、無効/使用できない状態のインスタンスを作成できるようになりますT。DTOochビューモデルのような非常に単純なものでない限り、これは避けます。
サラ

20

非常に古い質問ですが、新しい答えです;-)

ExpressionTreeバージョン:(私は最速で最もクリーンなソリューションだと思います)

同様WELLYタンブナンは言いました、「我々はまた、オブジェクトを構築するために式ツリーを使用することができます」

これにより、指定されたタイプ/パラメータの「コンストラクタ」(関数)が生成されます。デリゲートを返し、パラメーターの型をオブジェクトの配列として受け入れます。

ここにあります:

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

MyClassの例:

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

使用法:

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

ここに画像の説明を入力してください


別の例:型を配列として渡す

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

式のDebugView

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

これは、生成されるコードと同等です。

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

小さな欠点

すべてのvaluetypesパラメータは、オブジェクト配列のように渡されるとボックス化されます。


簡単なパフォーマンステスト:

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

結果:

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

の使用Expressionsは、呼び出しの+/- 8倍ConstructorInfoおよびの使用より +/- 20倍高速です。Activator


コンストラクターpublic MyClass(T data)を使用してMyClass <T>を構築する場合の対処法について洞察がありますか。この場合、Expression.Convertは例外をスローし、変換にジェネリック制約基本クラスを使用すると、コンストラクター情報がジェネリック型用であるため、Expression.Newがスローされます
Mason

@メイソン(しばらく答えました;-)) var myConstructor = CreateConstructor(typeof(MyClass<int>), typeof(int));これは問題なく動作しています。知りません。
Jeroen van Langen

19

これはあなたの状況では機能しません。空のコンストラクターがあるという制約のみを指定できます。

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

あなたができることは、このインターフェースを定義することによってプロパティ注入を使用することです:

public interface ITakesAListItem
{
   ListItem Item { set; }
}

次に、メソッドを次のように変更します。

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

もう1つのFunc方法は、JaredParによって記述された方法です。


これは、引数を取るコンストラクター内のロジックをバイパスしますよね?Jaredのアプローチのような何かをしたいのですが、クラスの内部でメソッドを呼び出しているので、具体的な型がわからない...
hmmm

3
右、これはT()のデフォルトコンストラクターのロジックを呼び出し、プロパティ "Item"を設定するだけです。デフォルト以外のコンストラクタのロジックを呼び出そうとしている場合、これは役に立ちません。
スコットスタッフォード

7

T:new()を追加して、Tがデフォルトのコンストラクターを提供することが保証されていることをコンパイラーに知らせる必要があります。

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

1
UPDATE:正しいエラーメッセージがある:「T」:変数のインスタンスを作成する際に引数を提供することができない
、LB

これは、空のコンストラクタを使用していないため、オブジェクトの引数を渡しています。ジェネリックTypeにnew(object)パラメータがあることを指定せずにそれを処理する方法はありません。
ミン

次に、次のいずれかを実行する必要があります。1.リフレクションを使用する2.コンストラクターではなく初期化メソッドにパラメーターを渡す。初期化メソッドは、型が実装するインターフェイスに属し、where T:...宣言。オプション1は残りのコードへの影響が最小ですが、オプション2はコンパイル時のチェックを提供します。
リチャード

リフレクションを使用しないでください!他の回答で概説されているように、同じ効果を得る他の方法があります。
Garry Shutler、

@ギャリー-リフレクションが必ずしも最善のアプローチであるとは限らないことに同意しますが、これにより、コードベースの残りの部分を最小限に変更するだけで、必要なことを実現できます。とは言っても、@ JaredParからのファクトリデリゲートアプローチの方がずっと好きです。
リチャード

7

コンストラクターパラメーターを使用してメンバーフィールドまたはプロパティを初期化するだけの場合は、C#> = 3で非常に簡単に行うことができます。

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が言ったことと同じですが、補足説明をしたいと思います。

もちろん、プロパティトリックを使用して、フィールド値を設定するだけではありません。プロパティ「set()」は、関連するフィールドのセットアップに必要な処理や、オブジェクト自体のその他の必要性をトリガーできます。これには、オブジェクトが使用される前に完全な初期化が行われるかどうかを確認し、完全な構造をシミュレートします(はい、これは醜い回避策ですが、M $のnew()の制限を克服しています)。

計画的なホールなのか、偶発的な副作用なのかはわかりませんが、問題はありません。

MSの人々が言語に新しい機能を追加し、完全な副作用分析を行っていないように見えるのは非常に面白いです。全体的なことはこれの良い証拠です...


1
両方の制約が必要です。InterfaceOrBaseClassは、コンパイラにフィールド/プロパティBaseMemberItemを認識させます。「new()」制約がコメント化されていると、エラーが発生します。エラー6 new()制約がないため、変数タイプ「T」のインスタンスを作成できません
fljx

私が遭遇した状況は、ここで尋ねられた質問と正確に同じではありませんでしたが、この答えは私がどこに行かなければならないかを導き、それは非常にうまく機能しているようです。
RubyHaus 2010

5
誰かがマイクロソフトを「M $」と呼ぶたびに、私の魂のごく一部が苦しんでいます。
Mathias Lykkegaard Lorenzen

6

「型パラメーターTのインスタンスを作成するときに引数を提供できない」というエラーが発生していることがわかりました。これを行う必要がありました。

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

5

使用するクラスにアクセスできる場合は、私が使用したこのアプローチを使用できます。

代替のクリエーターを持つインターフェースを作成します。

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

空のクリエーターでクラスを作成し、このメソッドを実装します。

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

次に、ジェネリックメソッドを使用します。

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

アクセス権がない場合は、ターゲットクラスをラップします。

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

これは一種の不器用で、私が一種の不器用と言うとき、私は反乱を意味するかもしれませんが、パラメーター化された型に空のコンストラクターを提供できると仮定すると、次のようになります。

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

引数付きのパラメーター化された型からオブジェクトを効率的に構築できます。この場合、必要なコンストラクターにtypeの引数が1つあると想定していobjectます。制約が許可された空のコンストラクターを使用してTのダミーインスタンスを作成し、リフレクションを使用して他のコンストラクターの1つを取得します。


0

プロパティインジェクションを使用した回答に似たアプローチを使用することもありますが、コードをよりクリーンに保ちます。プロパティのセットを持つ基本クラス/インターフェースを持つ代わりに、「貧乏人のコンストラクター」として機能する(仮想)Initialize()メソッドのみが含まれます。次に、コンストラクターと同じように、各クラスに独自の初期化を処理させることができます。これにより、継承チェーンを処理する便利な方法も追加されます。

チェーン内の各クラスにその固有のプロパティを初期化させ、その親のInitialize()メソッドを呼び出して親の固有のプロパティを初期化するような状況で頻繁に自分自身を見つけた場合など。これは、クラスが異なるが階層が類似している場合に特に便利です。たとえば、DTOに/からマップされるビジネスオブジェクトなどです。

初期化に共通の辞書を使用する例:

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

ListItemからT型への変換が必要な場合は、この変換を変換演算子としてTクラスに実装できます。

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

新しいコンストラクタを持つオブジェクトのみを許可するには、whereステートメントでTを制約する必要があると思います。

今では、それを持たないオブジェクトを含むすべてのものを受け入れます。


1
この回答を変更すると、この回答が文脈から外れるため、回答後に質問に編集されます。
シャトル87
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.