web-dev-qa-db-fra.com

Créer une instance de type générique?

Si BaseFruit a un constructeur qui accepte un int weight, puis-je instancier un fruit avec une méthode générique comme celle-ci?

public void AddFruit<T>()where T: BaseFruit{
    BaseFruit fruit = new T(weight); /*new Apple(150);*/
    fruit.Enlist(fruitManager);
}

Un exemple est ajouté derrière les commentaires. Il semble que je ne peux le faire que si je donne à BaseFruit un constructeur sans paramètre, puis que je remplis tout dans les variables membres. Dans mon vrai code (pas à propos de fruit), c'est plutôt impraticable.

-Mettre à jour-
Il semble donc que cela ne puisse être résolu par aucune contrainte. À partir des réponses, il existe trois solutions possibles

  • Modèle d'usine
  • Réflexion
  • Activateur

J'ai tendance à penser que la réflexion est la moins nette, mais je ne peux pas choisir entre les deux autres.

194
Boris Callens

En outre, un exemple plus simple:

return (T)Activator.CreateInstance(typeof(T), new object[] { weight });

Notez que l'utilisation de la contrainte new () sur T ne sert qu'à effectuer la vérification du compilateur pour un constructeur public sans paramètre à la compilation, le code utilisé pour créer le type est la classe Activator.

Vous devrez vous assurer que le constructeur existant existe déjà, et ce genre d'exigence peut être une odeur de code (ou plutôt quelque chose que vous devriez simplement essayer d'éviter dans la version actuelle sur c #).

284
meandmycode

Vous ne pouvez utiliser aucun constructeur paramétré. Vous pouvez utiliser un constructeur sans paramètre si vous avez une contrainte "where T : new()".

C'est une douleur, mais telle est la vie :(

C’est l’une des questions que je voudrais aborder avec "interfaces statiques" . Vous seriez alors en mesure de contraindre T à inclure des méthodes, des opérateurs et des constructeurs statiques, puis à les appeler.

83
Jon Skeet

Oui; changer votre endroit où être:

where T:BaseFruit, new()

Cependant, cela ne fonctionne qu'avec sans paramètre . Vous devrez avoir un autre moyen de définir votre propriété (définir la propriété elle-même ou quelque chose de similaire).

48
Adam Robinson

La solution la plus simple Activator.CreateInstance<T>()

21
user1471935

Comme Jon l'a souligné, il s'agit d'une contrainte majeure pour contraindre un constructeur sans paramètre. Cependant, une solution différente consiste à utiliser un modèle d'usine. C'est facilement contraignable 

interface IFruitFactory<T> where T : BaseFruit {
  T Create(int weight);
}

public void AddFruit<T>( IFruitFactory<T> factory ) where T: BaseFruit {    
  BaseFruit fruit = factory.Create(weight); /*new Apple(150);*/    
  fruit.Enlist(fruitManager);
}

Une autre option consiste à utiliser une approche fonctionnelle. Passer dans une méthode d'usine.

public void AddFruit<T>(Func<int,T> factoryDel) where T : BaseFruit { 
  BaseFruit fruit = factoryDel(weight); /* new Apple(150); */
  fruit.Enlist(fruitManager);
}
16
JaredPar

Vous pouvez le faire en utilisant la réflexion:

public void AddFruit<T>()where T: BaseFruit
{
  ConstructorInfo constructor = typeof(T).GetConstructor(new Type[] { typeof(int) });
  if (constructor == null)
  {
    throw new InvalidOperationException("Type " + typeof(T).Name + " does not contain an appropriate constructor");
  }
  BaseFruit fruit = constructor.Invoke(new object[] { (int)150 }) as BaseFruit;
  fruit.Enlist(fruitManager);
}

MODIFIER: Constructeur ajouté == null check.

MODIFIER: Une variante plus rapide utilisant un cache:

public void AddFruit<T>()where T: BaseFruit
{
  var constructor = FruitCompany<T>.constructor;
  if (constructor == null)
  {
    throw new InvalidOperationException("Type " + typeof(T).Name + " does not contain an appropriate constructor");
  }
  var fruit = constructor.Invoke(new object[] { (int)150 }) as BaseFruit;
  fruit.Enlist(fruitManager);
}
private static class FruitCompany<T>
{
  public static readonly ConstructorInfo constructor = typeof(T).GetConstructor(new Type[] { typeof(int) });
}
11
mmmmmmmm

J'ai créé cette méthode:

public static V ConvertParentObjToChildObj<T,V> (T obj) where V : new()
{
    Type typeT = typeof(T);
    PropertyInfo[] propertiesT = typeT.GetProperties();
    V newV = new V();
    foreach (var propT in propertiesT)
    {
        var nomePropT = propT.Name;
        var valuePropT = propT.GetValue(obj, null);

        Type typeV = typeof(V);
        PropertyInfo[] propertiesV = typeV.GetProperties();
        foreach (var propV in propertiesV)
        {
            var nomePropV = propV.Name;
            if(nomePropT == nomePropV)
            {
                propV.SetValue(newV, valuePropT);
                break;
            }
        }
    }
    return newV;
}

Je l'utilise de cette façon:

public class A 
{
    public int PROP1 {get; set;}
}

public class B : A
{
    public int PROP2 {get; set;}
}

Code:

A instanceA = new A();
instanceA.PROP1 = 1;

B instanceB = new B();
instanceB = ConvertParentObjToChildObj<A,B>(instanceA);
1

En complément à la suggestion de user1471935: 

Pour instancier une classe générique à l'aide d'un constructeur avec un ou plusieurs paramètres, vous pouvez maintenant utiliser la classe Activator.

T instance = Activator.CreateInstance(typeof(T), new object[] {...}) 

La liste des objets sont les paramètres que vous souhaitez fournir. Selon Microsoft :

CreateInstance [...] crée une instance du type spécifié en utilisant le constructeur qui correspond le mieux aux paramètres spécifiés.

Il existe également une version générique de CreateInstance (CreateInstance<T>()) mais celle-ci ne vous permet pas non plus de fournir des paramètres de constructeur. 

0
Rob Vermeulen

Récemment, je suis tombé sur un problème très similaire. Je voulais juste partager notre solution avec vous tous. Je voulais créer une instance de Car<CarA> à partir d'un objet json utilisant un enum:

Dictionary<MyEnum, Type> mapper = new Dictionary<MyEnum, Type>();

mapper.Add(1, typeof(CarA));
mapper.Add(2, typeof(BarB)); 

public class Car<T> where T : class
{       
    public T Detail { get; set; }
    public Car(T data)
    {
       Detail = data;
    }
}
public class CarA
{  
    public int PropA { get; set; }
    public CarA(){}
}
public class CarB
{
    public int PropB { get; set; }
    public CarB(){}
}

var jsonObj = {"Type":"1","PropA":"10"}
MyEnum t = GetTypeOfCar(jsonObj);
Type objectT = mapper[t]
Type genericType = typeof(Car<>);
Type carTypeWithGenerics = genericType.MakeGenericType(objectT);
Activator.CreateInstance(carTypeWithGenerics , new Object[] { JsonConvert.DeserializeObject(jsonObj, objectT) });
0
farshid