2009-04-08 7 views
162

Si BaseFruit a un constructeur qui accepte un int weight, puis-je instancier un fruit dans une méthode générique comme celle-ci?Créer une instance de type générique?

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 et ensuite remplir tout à travers les variables membres. Dans mon vrai code (pas sur les fruits), c'est plutôt impraticable.

-Update-
Il semble donc qu'il ne peut pas être résolu par des contraintes de quelque façon alors. D'après les réponses, il y a trois solutions candidats:

  • Motif usine
  • de réflexion
  • Activator

Je tends à penser la réflexion est la moins propre, mais je ne peux pas décider entre la deux autres.

+0

BTW: aujourd'hui je résoudrais probablement cela avec la bibliothèque IoC de choix. –

Répondre

238

De plus, un exemple plus simple:

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

Notez que l'utilisation de la nouvelle() contrainte sur T est seulement de faire le compilateur vérifier un constructeur public sans paramètre à la compilation, le code réel utilisé pour créer le type est la classe Activator.

Vous devrez vous assurer du constructeur spécifique existant, 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#).

+0

Puisque ce constructeur est sur le baseclass (BaseFruit) je sais qu'il aura un constructeur. Mais en effet, si un jour je décide que basefruit a besoin de plus de paramètres, je pourrais être foutu. Regardera dans la classe ACtivator cependant. Je n'en ai pas entendu parler auparavant. –

+3

Celui-ci a bien fonctionné. Il existe également une procédure CreateInstance (), mais celle-ci n'a pas de surcharge pour les paramètres de certains rasons. –

+8

Il n'est pas nécessaire d'utiliser 'new object [] {weight}'. 'CreateInstance' est déclaré avec params,' public static object CreateInstance (Type type, params object [] args), donc vous pouvez simplement faire 'return (T) Activator.CreateInstance (typeof (T), poids);'. S'il existe plusieurs paramètres, transmettez-les en tant qu'arguments séparés. Seulement si vous avez déjà un énumérable construit de paramètres, vous devriez le convertir en 'object []' et le passer à 'CreateInstance'. – ErikE

40

Oui; changer votre où être:

where T:BaseFruit, new() 

Cependant, cela ne fonctionne qu'avec constructeurs parameterless. Vous devrez avoir d'autres moyens de définir votre propriété (définir la propriété elle-même ou quelque chose de similaire).

+42

Darn, j'ai Robinsoned ... –

+3

@Jon Skeet: Qui est venu très près de me faire rire à haute voix (au travail!). –

+1

@MichaelMyers Je n'étais pas _that lucky_ – ppeterka

74

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

Il est une douleur, mais telle est la vie :(

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

+1

Vous êtes tous fruité! :) –

+10

Je voulais juste dire Skeeted. –

+1

Au moins, vous pouvez faire de telles contraintes - Java me déçoit toujours. –

13

Comme Jon a souligné c'est la vie pour contraindre un constructeur non parameterless. Cependant, une autre solution est d'utiliser un modèle d'usine. Ceci est facilement constrainable

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 est d'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); 
} 
+2

Bonne suggestion - bien que si vous ne faites pas attention, vous pouvez finir dans l'enfer de l'API Java DOM, avec des usines à gogo :( –

+0

@Jon, ne voudrait pas que :) – JaredPar

+0

Oui, c'est une solution que je cherchais moi même. Mais j'espérais quelque chose dans la ligne des contraintes. Je suppose que non .. –

10

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

EDIT: constructeur Ajouté == null chèque.

EDIT: Une variante plus rapide en 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) }); 
} 
+0

Bien que je n'aime pas les frais généraux de la réflexion, comme d'autres l'ont expliqué, c'est juste comme ça actuellement. Voyant comment ce constructeur ne sera pas appelé trop, je pourrais aller avec cela. Ou l'usine. Je ne sais pas encore. –

17

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

+0

Directement au but! tu as mon vote –

0

Récemment, je suis tombé sur un problème très similaire. Je voulais juste partager notre solution avec vous tous. Je voulais que je créé une instance d'un Car<CarA> d'un objet JSON en utilisant ce qui avait 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) }); 
-2

Il est encore possible, avec des performances élevées, en procédant comme suit:

// 
    public List<R> GetAllItems<R>() where R : IBaseRO, new() { 
     var list = new List<R>(); 
     using (var wl = new ReaderLock<T>(this)) { 
      foreach (var bo in this.items) { 
       T t = bo.Value.Data as T; 
       R r = new R(); 
       r.Initialize(t); 
       list.Add(r); 
      } 
     } 
     return list; 
    } 

et

// 
///<summary>Base class for read-only objects</summary> 
public partial interface IBaseRO { 
    void Initialize(IDTO dto); 
    void Initialize(object value); 
} 

Les classes concernées doivent ensuite dériver de cette interface et s'initialiser en conséquence. Veuillez noter, que dans mon cas, ce code fait partie d'une classe environnante, qui a déjà <T> comme paramètre générique. R, dans mon cas, est également une classe en lecture seule. IMO, la disponibilité publique des fonctions Initialize() n'a aucun effet négatif sur l'immutabilité. L'utilisateur de cette classe pourrait placer un autre objet, mais cela ne modifierait pas la collection sous-jacente.

Questions connexes