it-swarm.com.de

Übergeben von Argumenten an C # generic new () mit Vorlagen

Ich versuche, ein neues Objekt vom Typ T über seinen Konstruktor zu erstellen, wenn ich es der Liste hinzufüge.

Ich erhalte einen Kompilierungsfehler: Die Fehlermeldung lautet:

'T': Beim Erstellen einer Instanz einer Variablen können keine Argumente angegeben werden

Aber meine Klassen haben ein Konstruktorargument! Wie kann ich das schaffen?

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

Um eine Instanz eines generischen Typs in einer Funktion zu erstellen, müssen Sie sie mit dem Flag "new" einschränken.

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

Dies funktioniert jedoch nur, wenn Sie den Konstruktor aufrufen möchten, der keine Parameter hat. Hier ist das nicht der Fall. Stattdessen müssen Sie einen anderen Parameter angeben, der die Erstellung von Objekten auf der Grundlage von Parametern ermöglicht. Am einfachsten ist eine Funktion.

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

Sie können es dann so nennen

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

in .Net 3.5 und nachdem Sie die Activator-Klasse verwenden konnten:

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

Da sich niemand die Mühe gemacht hat, die "Reflection" -Antwort (die meiner Meinung nach die beste Antwort ist) zu posten, gilt Folgendes:

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

Bearbeiten: Diese Antwort ist aufgrund von Activator.CreateInstance in .NET 3.5 veraltet. Sie ist jedoch in älteren .NET-Versionen weiterhin nützlich.

51
James Jones

Objektinitialisierer

Wenn Ihr Konstruktor mit dem Parameter nichts anderes tut als eine Eigenschaft festzulegen, können Sie dies in C # 3 oder besser mit einem Objektinitialisierer tun, anstatt einen Konstruktor aufzurufen (was, wie bereits erwähnt, unmöglich ist ):

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

Auf diese Weise können Sie auch immer eine beliebige Konstruktorlogik in den Standardkonstruktor (leer) einfügen.

Activator.CreateInstance ()

Alternativ können Sie Activator.CreateInstance () wie folgt aufrufen:

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

Beachten Sie, dass Activator.CreateInstance einige Performance-Overhead haben kann, die Sie möglicherweise vermeiden möchten, wenn die Ausführungsgeschwindigkeit oberste Priorität hat und eine andere Option für Sie wartbar ist.

29
Tim Lehner

Dies wird in Ihrer Situation nicht funktionieren. Sie können nur die Einschränkung angeben, für die ein leerer Konstruktor vorhanden ist:

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

Sie können die Eigenschaftsinjektion verwenden, indem Sie diese Schnittstelle definieren:

public interface ITakesAListItem
{
   ListItem Item { set; }
}

Dann können Sie Ihre Methode folgendermaßen ändern:

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

Die andere Alternative ist die von JaredPar beschriebene Methode Func.

18
Garry Shutler

Sehr alte Frage, aber neue Antwort ;-)

Die ExpressionTree-Version : (Ich denke die schnellste und sauberste Lösung)

Wie Welly Tambunan sagte, "wir könnten auch Ausdrucksbaum verwenden, um das Objekt zu bauen"

Dies erzeugt einen 'Konstruktor' (Funktion) für den angegebenen Typ/Parameter. Es gibt einen Delegaten zurück und akzeptiert die Parametertypen als Array von Objekten.

Hier ist es:

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

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

Verwendung:

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


Ein weiteres Beispiel: Übergeben der Typen als Array

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 von Expression

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

Dies entspricht dem generierten Code:

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

Kleiner Nachteil

Alle Wertetyp-Parameter werden in Kästchen angezeigt, wenn sie wie ein Objektarray übergeben werden.


Einfacher Leistungstest:

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

Ergebnisse:

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

Die Verwendung von Expressions ist +/- 8-mal schneller als das Aufrufen von ConstructorInfo und +/- 20-mal schneller als mit dem Activator

17

Sie müssen where T: new () hinzufügen, um dem Compiler mitzuteilen, dass T garantiert einen Standardkonstruktor bereitstellt.

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

Wenn Sie einfach ein Elementfeld oder eine Eigenschaft mit dem Konstruktorparameter initialisieren möchten, können Sie dies in C #> = 3 sehr einfach tun:

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

Dies ist dasselbe, was Garry Shutler gesagt hat, aber ich möchte eine zusätzliche Notiz hinzufügen.

Natürlich können Sie mit einem Eigenschaftstrick mehr tun, als nur einen Feldwert festzulegen. Eine Eigenschaft "set ()" kann eine Verarbeitung auslösen, die zum Einrichten der zugehörigen Felder und anderer Anforderungen für das Objekt selbst erforderlich ist, einschließlich einer Überprüfung, ob eine vollständige Initialisierung stattfinden soll, bevor das Objekt verwendet wird, wobei eine vollständige Konstruktion simuliert wird ( Ja, es ist eine hässliche Problemumgehung, aber sie überwindet die new () - Einschränkung von M $.

Ich kann nicht sicher sein, ob es sich um ein geplantes Loch oder eine zufällige Nebenwirkung handelt, aber es funktioniert.

Es ist sehr lustig, wie MS-Leute der Sprache neue Funktionen hinzufügen und keine vollständige Analyse der Nebenwirkungen durchführen. Das gesamte Generikum ist ein guter Beweis dafür ...

7
fljx

Ich stellte fest, dass ich die Fehlermeldung "Beim Erstellen einer Instanz des Typparameters T können keine Argumente angegeben werden" erhalten habe. Dazu musste ich Folgendes tun:

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

Wenn Sie Zugriff auf die Klasse haben, die Sie verwenden möchten, können Sie diesen Ansatz verwenden, den ich verwendet habe.

Erstellen Sie eine Schnittstelle mit einem alternativen Ersteller:

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

Erstellen Sie Ihre Klassen mit einem leeren Ersteller und implementieren Sie diese Methode:

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

Verwenden Sie nun Ihre generischen Methoden:

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

Wenn Sie keinen Zugriff haben, verpacken Sie die Zielklasse:

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

Das ist ein bisschen munter, und wenn ich "ein bisschen munter" sage, dann meine ich vielleicht "Aufruhr", aber wenn Sie Ihren parametrisierten Typ mit einem leeren Konstruktor ausstatten können, dann:

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

Ermöglicht es Ihnen effektiv, ein Objekt aus einem parametrisierten Typ mit einem Argument zu konstruieren. In diesem Fall gehe ich davon aus, dass der gewünschte Konstruktor ein einziges Argument vom Typ object hat. Wir erstellen eine Dummy-Instanz von T unter Verwendung des leeren Konstruktors mit der Einschränkung "Zulässig" und verwenden dann Reflektion, um einen seiner anderen Konstruktoren abzurufen.

0
silasdavis

Ich benutze manchmal einen Ansatz, der den Antworten mit der Eigenschaftsinjektion ähnelt, aber den Code sauberer hält. Anstatt eine Basisklasse/Schnittstelle mit einer Reihe von Eigenschaften zu haben, enthält sie nur eine (virtuelle) Initialize () - Methode, die als Konstruktor eines "armen Mannes" fungiert. Anschließend können Sie jede Klasse wie einen Konstruktor mit ihrer eigenen Initialisierung beauftragen, wodurch sich auch die Handhabung von Vererbungsketten vereinfacht.

Wenn ich mich oft in Situationen befinde, in denen ich möchte, dass jede Klasse in der Kette ihre eindeutigen Eigenschaften initialisiert, und dann die Initialize () -Methode ihrer Eltern aufruft, die wiederum die eindeutigen Eigenschaften der Eltern initialisiert und so weiter. Dies ist besonders nützlich, wenn verschiedene Klassen, jedoch mit einer ähnlichen Hierarchie, vorhanden sind, z. B. Geschäftsobjekte, die DTOs zugeordnet sind.

Beispiel, das ein allgemeines Wörterbuch für die Initialisierung verwendet:

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