2009-04-08 12 views
162

Se BaseFruit ha un costruttore che accetta un int weight, posso creare un'istanza di un pezzo di frutta in un metodo generico come questo?Crea istanza di tipo generico?

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

Un commento è stato aggiunto dietro i commenti. Sembra che posso farlo solo se io do a BaseFruit un costruttore senza parametri e poi riempi tutto attraverso le variabili membro. Nel mio vero codice (non sulla frutta) questo è piuttosto poco pratico.

-Ricalcola-
Così sembra che non può essere risolto con i vincoli in alcun modo allora. Dalle risposte ci sono tre soluzioni candidate:

  • fabbrica modello
  • Riflessione
  • Activator

Tendo a pensare riflessione è il meno pulito, ma non riesco a decidere tra la altri due.

+0

BTW: oggi probabilmente risolverei questo con la libreria di IoC di scelta. –

risposta

238

Inoltre un esempio più semplice:

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

Si noti che utilizzando il nuovo vincolo() su T è solo quello di rendere il compilatore verificare la presenza di un costruttore pubblico parametrico in fase di compilazione, il codice effettivo utilizzato per creare il tipo è la classe Activator.

Sarà necessario garantire stessi per quanto riguarda il costruttore specifica esistente, e questo tipo di esigenza può essere un odore di codice (o meglio, qualcosa che si dovrebbe solo cercare di evitare nella versione corrente su C#).

+0

Dato che questo costruttore si trova sulla baseclass (BaseFruit), so che avrà un costruttore. Ma in effetti, se un giorno deciderò che la basefrutta ha bisogno di più parametri, potrei essere fregata. Tuttavia, esamineremo la classe di ACtivator. Non ho sentito prima. –

+3

Questo ha funzionato bene. Esiste anche una procedura CreateInstance (), ma che non ha un sovraccarico per i parametri per alcuni rason. –

+8

Non è necessario usare 'new object [] {weight}'. 'CreateInstance' è dichiarato con parametri,' oggetto statico pubblico CreateInstance (tipo Tipo, oggetto params [] args) ', quindi puoi semplicemente fare' return (T) Activator.CreateInstance (typeof (T), weight); '. Se ci sono più parametri, passali come argomenti separati. Solo se hai già una enumerabile quantità di parametri dovresti preoccuparti di convertirlo in 'oggetto []' e passare a 'CreateInstance'. – ErikE

40

Sì; cambiare la vostra dove essere:

where T:BaseFruit, new() 

Tuttavia, questo funziona solo con senza parametri costruttori. Dovrai avere altri mezzi per impostare la tua proprietà (impostando la proprietà stessa o qualcosa di simile).

+42

Dannazione, mi sono fatto prendere da Robinson ... –

+3

@Jon Skeet: È venuto molto vicino a farmi ridere ad alta voce (al lavoro!). –

+1

@MichaelMyers I was not _that lucky_ – ppeterka

74

Non è possibile utilizzare alcun costruttore parametrizzato. È possibile utilizzare un costruttore senza parametri se si dispone di un vincolo "where T : new()".

è un dolore, ma così è la vita :(

Questa è una delle cose che mi piacerebbe affrontare con "static interfaces". Si sarebbe quindi in grado di limitare T per includere statici metodi, operatori e costruttori , e poi li chiamano.

+1

Sei tutto fruttato! :) –

+10

Volevo solo dire Skeeted. –

+1

Almeno tu PUOI fare tali vincoli - Java mi delude sempre. –

13

Come Jon ha sottolineato questa è la vita per vincolare un costruttore non senza parametri. Tuttavia una soluzione diversa è quella di utilizzare un modello di fabbrica. questo è facilmente vincolabile

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

ancora un'altra opzione è da usare un approccio funzionale Passare in un metodo di fabbrica

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

Buon suggerimento - anche se non stai attento puoi finire all'inferno dell'API Java DOM, con le fabbriche a bizzeffe :( –

+0

@Jon, non vorrei che :) – JaredPar

+0

Sì, questa è una soluzione che stavo considerando me stessa. Ma stavo sperando in qualcosa nella linea dei vincoli. Indovina non poi .. –

10

si può fare utilizzando riflessione:

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: Aggiunto costruttore == null check.

EDIT: Una variante più veloce utilizzando una 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

Anche se non mi piace il sovraccarico del riflesso, come altri hanno spiegato, questo è solo il modo in cui è attualmente. Vedendo come questo costruttore non sarà chiamato troppo, potrei andare con questo. O la fabbrica. Non lo so ancora. –

17

soluzione più semplice Activator.CreateInstance<T>()

+0

dritto al punto! hai il mio voto –

0

Recentemente mi sono imbattuto in un problema molto simile. Volevo solo condividere la nostra soluzione con tutti voi. Ho voluto ho creato un'istanza di un Car<CarA> da un oggetto JSON con il quale ha avuto 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

E 'ancora possibile, ad alte prestazioni, effettuando le seguenti operazioni:

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

e

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

Le classi rilevanti devono quindi derivare da questa interfaccia e inizializzarsi di conseguenza. Si noti che, nel mio caso, questo codice fa parte di una classe circostante, che ha già <T> come parametro generico. R, nel mio caso, è anche una classe di sola lettura. IMO, la disponibilità pubblica delle funzioni Initialize() non ha effetti negativi sull'immutabilità. L'utente di questa classe potrebbe inserire un altro oggetto, ma ciò non modificherà la raccolta sottostante.