374


115

テンプレート型のC#generic new()に引数を渡す

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

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

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

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

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

13 Answer


381


関数内にジェネリック型のインスタンスを作成するためには、それを "new"フラグで制約しなければなりません。

public static string GetAllItems(...)T:new()

しかし、それはあなたがパラメータを持たないコンストラクタを呼び出したいときにだけうまくいくでしょう。 そうではありません。 代わりに、パラメータに基づいてオブジェクトを作成するための別のパラメータを指定する必要があります。 最も簡単なのは関数です。

パブリック静的文字列GetAllItems(...、Func del){... リストtabListItems = new List(); foreach(listCollection内のListItem、listItem){tabListItems.Add(del(listItem)); ... }

あなたはそれからそう呼ぶことができます

GetAllItems(...、l => new Foo(l));


305


NET 3.5以降では、アクティベータクラスを使用できます。
(T)Activator.CreateInstance(typeof(T)、args)


51


'Reflection’の回答(私が個人的には最良の回答だと思う)を投稿することに悩む人はいないので、ここに行きます。

public static string GetAllItems(...)ここでT:new(){... リストtabListItems = new List(); foreach(listCollection内のListItem、listItem){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バージョンでは有用です。


27


オブジェクト初期化子

パラメータを設定したコンストラクタがプロパティを設定する以外に何もしていない場合は、C#3以降でhttp://msdn.microsoft.com/en-us/library/bb384062(v=vs.100を使用してこれを実行できます。 )コンストラクターを呼び出すのではなく.aspx [オブジェクト初期化子](前述のように不可能です):

public static string GetAllItems(...)ここでT:new(){... リストtabListItems = new List(); foreach(listCollection内のListItem、listItem){tabListItems.Add(new T(){YourPropertyName = listItem}); //オブジェクト初期化子を使用しています} ... }

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

  • Activator.CreateInstance()*

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

public static string GetAllItems(...)ここでT:new(){... リストtabListItems = new List(); foreach(listCollection内のListItem、listItem){object [] args = new object [] {listItem}; tabListItems.Add((T)Activator.CreateInstance(typeof(T)、args)); // Activator.CreateInstanceを使用しています} ... }

Activator.CreateInstanceには、必要な パフォーマンスオーバーヘッドを含めることができます実行速度が最優先であり、他の選択肢があなたにとって維持可能であるならば避けてください。


17


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

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

あなたができることはこのインターフェースを定義することによってプロパティインジェクションを使うことです:

public interface ITakesAListItem
{
   ListItem Item { set; }
}

それからあなたはあなたの方法をこれに変更することができます:

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

他の選択肢は、JaredParによって記述された `+ Func +`メソッドです。


14


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

  • ExpressionTreeバージョン*:(私は最速かつ最もクリーンな解決策だと思います)

_Welly Tambunan_が述べたように、 "オブジェクトを構築するために式ツリーを使うこともできます" _

与えられた型/パラメータに対して 'コンストラクタ'(関数)を生成します。 これはデリゲートを返し、パラメータの型をオブジェクトの配列として受け取ります。

ここにあります:

// 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(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` fucntion 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(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 `を使用すると、 ` ConstructorInfo `を呼び出すよりも/- * 8倍速くなり、 `+ Activator `を使用するよりも/- * 20倍速くなります*


7


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

public static string GetAllItems(...)T:new()


7


単にメンバフィールドやプロパティをコンストラクタパラメータで初期化したいのであれば、C#> = 3ではとても簡単にできます。

public static string GetAllItems(...)T:InterfaceOrBaseClass、new(){... リストtabListItems = new List(); foreach(listCollection内のListItem、listItem){tabListItems.Add(new T {BaseMemberItem = listItem}); //エラーなし、BaseMemberItemはInterfaceOrBaseClassを所有しています。 ... }

これはGarry Shutlerが言ったのと同じことですが、私は追加のメモを書きたいのですが。

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

それが計画された穴なのか偶然の副作用なのか私は確信できませんが、それはうまくいきます。

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


6


「タイプTのインスタンスを作成するときに引数を指定することはできません」というエラーが発生していることがわかったので、これを行う必要がありました。

Tとしてvar x = Activator.CreateInstance(typeof(T)、args)。


2


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

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

パブリックインターフェイスICreatable1Param {void PopulateInstance(object Param);} }

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

パブリッククラスMyClass:ICreatable1Param {public MyClass(){//何かをするか、または何もしない} public void PopulateInstance(object Param){//ここにクラスを追加する}}

今すぐあなたの一般的な方法を使用してください:

public void MyMethod(...)ここで、T:ICreatable1Param、new(){// Tを実行しますT newT = new T(); T.PopulateInstance(パラメータ); }

'' '' '

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

パブリッククラスMyClass:ICreatable1Param {public WrappedClass WrappedInstance {get;}プライベートセット} public MyClass(){//何か、あるいは何もしない} public void PopulateInstance(object Param){WrappedInstance = new WrappedClass(Param);} }}