web-dev-qa-db-fra.com

Passage d'arguments à C # generic new () de type basé sur un modèle

J'essaie de créer un nouvel objet de type T via son constructeur lors de l'ajout à la liste.

Je reçois une erreur de compilation: le message d'erreur est le suivant:

'T': impossible de fournir des arguments lors de la création d'une instance d'une variable

Mais mes classes ont un argument constructeur! Comment puis-je faire ce travail?

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.

Pour créer une instance d'un type générique dans une fonction, vous devez la contraindre avec le drapeau "new".

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

Toutefois, cela ne fonctionnera que si vous souhaitez appeler le constructeur qui n'a pas de paramètre. Pas le cas ici. Au lieu de cela, vous devrez fournir un autre paramètre permettant de créer un objet en fonction de paramètres. Le plus simple est une fonction.

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

Vous pouvez alors l'appeler comme si

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

dans .Net 3.5 et après que vous puissiez utiliser la classe d'activateur:

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

Puisque personne n'a pris la peine de poster la réponse "Réflexion" (que je pense personnellement être la meilleure réponse), voici:

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

Edit: Cette réponse est obsolète en raison de Activator.CreateInstance de .NET 3.5, mais elle reste utile dans les anciennes versions .NET.

51
James Jones

Initialiseur d'objet

Si votre constructeur avec le paramètre ne fait rien d'autre que la définition d'une propriété, vous pouvez le faire en C # 3 ou mieux en utilisant un initialiseur d'objet plutôt qu'en appelant un constructeur (ce qui est impossible, comme cela a été mentionné ):

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

En utilisant cela, vous pouvez toujours mettre n'importe quelle logique de constructeur dans le constructeur par défaut (vide).

Activator.CreateInstance ()

Vous pouvez aussi appeler Activator.CreateInstance () comme suit:

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

Notez que Activator.CreateInstance peut avoir un certain surcharge de performances que vous voudrez peut-être éviter si la vitesse d'exécution est une priorité absolue et qu'une autre option peut être gérée.

29
Tim Lehner

Cela ne fonctionnera pas dans votre situation. Vous pouvez uniquement spécifier à la contrainte qu'elle a un constructeur vide:

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

Ce que vous pourriez faire est d'utiliser l'injection de propriété en définissant cette interface:

public interface ITakesAListItem
{
   ListItem Item { set; }
}

Ensuite, vous pouvez modifier votre méthode pour qu'elle soit la suivante:

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

L’autre alternative est la méthode Func décrite par JaredPar.

18
Garry Shutler

Très vieille question, mais nouvelle réponse ;-)

La version d'ExpressionTree : (Je pense que la solution la plus rapide et la plus propre)

Comme Welly Tambunan a dit: "nous pourrions également utiliser l'arbre d'expression pour construire l'objet"

Cela générera un "constructeur" (fonction) pour le type/les paramètres donnés. Il retourne un délégué et accepte les types de paramètres sous forme de tableau d'objets.

C'est ici:

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

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

Utilisation:

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


Autre exemple: passer les types sous forme de tableau

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

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

Ceci est équivalent au code généré:

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

Petit inconvénient

Tous les paramètres valuetypes sont mis en boîte lorsqu'ils sont passés comme un tableau d'objets.


Test de performance simple:

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

Résultats:

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

Utiliser Expressions est +/- 8 fois plus rapide que l'invocation de ConstructorInfo et +/- 20 fois plus rapide que l’utilisation de la Activator

17
Jeroen van Langen

Vous devez ajouter où T: new () pour que le compilateur sache que T est assuré de fournir un constructeur par défaut.

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

Si vous voulez simplement initialiser un champ membre ou une propriété avec le paramètre constructeur, vous pouvez le faire très facilement en 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. 
   }  
   ... 
} 

C'est la même chose que Garry Shutler a dite, mais j'aimerais mettre une note complémentaire.

Bien sûr, vous pouvez utiliser une astuce de propriété pour faire plus que simplement définir une valeur de champ. Une propriété "set ()" peut déclencher tout traitement nécessaire à la configuration de ses champs associés et tout autre besoin relatif à l'objet lui-même, y compris un contrôle permettant de déterminer si une initialisation complète doit avoir lieu avant que l'objet ne soit utilisé, simulant ainsi une contruction complète ( oui, c'est une solution de contournement laide, mais elle surmonte la limitation new () de M $.

Je ne peux pas vous assurer qu'il s'agit d'un trou planifié ou d'un effet secondaire accidentel, mais cela fonctionne.

Il est très amusant de constater que les personnes atteintes de SEP ajoutent de nouvelles fonctionnalités à la langue et ne semblent pas effectuer une analyse complète des effets secondaires. Le générique entier en est une bonne preuve ...

7
fljx

J'ai trouvé que j'obtenais une erreur "je ne peux pas fournir d'arguments lors de la création d'une instance de type paramètre T", donc je devais le faire:

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

Si vous avez accès à la classe que vous allez utiliser, vous pouvez utiliser cette approche que j'ai utilisée.

Créez une interface qui a un créateur alternatif:

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

Créez vos classes avec un créateur vide et implémentez cette méthode:

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

Maintenant, utilisez vos méthodes génériques:

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

Si vous n'y avez pas accès, enveloppez la classe cible:

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

C'est un peu mucky, et quand je dis un peu mucky, je veux peut-être dire révoltant, mais en supposant que vous puissiez fournir votre type paramétré avec un constructeur vide, alors:

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

Vous permettra effectivement de construire un objet à partir d'un type paramétré avec un argument. Dans ce cas, je suppose que le constructeur que je veux a un seul argument de type object. Nous créons une instance factice de T en utilisant le constructeur vide autorisé par contrainte, puis utilisons la réflexion pour obtenir l'un de ses autres constructeurs.

0
silasdavis

J'utilise parfois une approche qui ressemble aux réponses en utilisant l'injection de propriété, mais en maintenant le code plus propre. Au lieu d'avoir une classe/interface de base avec un ensemble de propriétés, elle ne contient qu'une méthode Initialize () (virtuelle) qui agit comme un "constructeur de l'homme pauvre". Ensuite, vous pouvez laisser chaque classe gérer sa propre initialisation, comme le ferait un constructeur, ce qui ajoute également un moyen pratique de gérer les chaînes d'héritage.

Si je me trouve souvent dans des situations où je veux que chaque classe de la chaîne initialise ses propriétés uniques, puis appelle la méthode Initialize () de son parent, qui initialise à son tour les propriétés uniques du parent, etc. Cela est particulièrement utile lorsque vous avez différentes classes, mais avec une hiérarchie similaire, par exemple les objets métier mappés vers/à partir de DTO: s.

Exemple utilisant un dictionnaire commun pour l'initialisation:

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