web-dev-qa-db-ja.com

テンプレートタイプのC#generic new()に数を渡る

リストに追加するときに、コンストラクタを介して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.
   } 
   ...
}
382
LB.

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

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

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

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

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

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バージョンではまだ役に立ちます。

51
James Jones

オブジェクト初期化子

パラメータを持つコンストラクタがプロパティを設定する以外に何もしない場合は、コンストラクタを呼び出すのではなく オブジェクト初期化子 を使用して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には、実行速度が最優先で別のオプションが維持可能な場合は避けるべきいくつかの パフォーマンスオーバーヘッド があることに注意してください。

28
Tim Lehner

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

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

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

17
Garry Shutler

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

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<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` fucntion to create a new instance.
var myObject = myConstructor(10, "test message");

enter image description here


もう1つの例:型を配列として渡す

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を使用すると、ConstructorInfoを呼び出すよりも+/-の方が8倍速く、+/-よりも20倍高速ですActivatorを使うより

15

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

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

単にメンバフィールドやプロパティをコンストラクタパラメータで初期化したいのであれば、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()"は、オブジェクトが使用される前に完全初期化が行われるかどうかをチェックし、完全なcontructionをシミュレートするなど、関連フィールドの設定に必要な処理やオブジェクト自体に対するその他の必要性をトリガーできます。はい、それは醜い回避策ですが、それはM $のnew()制限を克服します)。

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

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

7
fljx

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

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

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

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

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);
    }
}
3
Daniel Möller

これは一種のムッキーです、そして私が一種のムッキーと言うとき私は回転することを意味するかもしれません、しかしあなたが空のコンストラクタであなたのパラメータ化されたタイプを供給することができると仮定すれば:

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

引数を使ってパラメータ化された型からオブジェクトを構築することを効果的に可能にします。この場合、欲しいコンストラクタがobject型の単一の引数を持つと仮定します。制約許可された空のコンストラクタを使用してTのダミーインスタンスを作成し、次にリフレクションを使用して他のコンストラクタの1つを取得します。

0
silasdavis

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

チェーン内の各クラスにその固有のプロパティを初期化してもらい、そのあと親のInitialize()メソッドを呼び出して親の固有のプロパティを初期化するなどの状況でよく見かける場合があります。これは、DTOとの間でマッピングされるビジネスオブジェクトなど、異なるクラスを使用しているが類似の階層を持つ場合に特に便利です。

初期化に共通のDictionaryを使用する例

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
Anders