2012-04-13 4 views
5

Ho lavorato a un problema per un paio d'ore e penso di essere vicino. Sto lavorando a un'app in cui potremmo avere 50-100 tipi che funzionano allo stesso modo. Così, invece di creare classi 50-100, ho cercato di rendere più generico e questo è quello che ho:Creare un tipo generico con interfaccia generica in fase di esecuzione

Questa è la classe base:

public class RavenWriterBase<T> : IRavenWriter<T> where T : class, IDataEntity 

E questa è l'interfaccia:

public interface IRavenWriter<T> 
{ 
    int ExecutionIntervalInSeconds { get; } 
    void Execute(object stateInfo); 
    void Initialize(int executionIntervalInSeconds, Expression<Func<T, DateTime>> timeOrderByFunc); 
} 

Ed è così che lo sto usando:

private static void StartWriters() 
{ 
    Assembly assembly = typeof(IDataEntity).Assembly; 
    List<IDataEntity> dataEntities = ReflectionUtility.GetObjectsForAnInterface<IDataEntity>(assembly); 

    foreach (IDataEntity dataEntity in dataEntities) 
    { 
     Type dataEntityType = dataEntity.GetType(); 
     Type ravenWriterType = typeof(RavenWriterBase<>).MakeGenericType(dataEntityType); 

     Expression<Func<IDataEntity, DateTime>> func = x => x.CicReadTime; 

     // This is where I'm stuck. How do I activate this as RavenWriterBase<T>? 
     var ravenWriter = Activator.CreateInstance(ravenWriterType); 

     //ravenWriter.Initialize(60, func); // I can't do this until I cast. 

     // More functionality here (not part of this issue) 
    } 
} 

mi sono bloccato su questa linea dall'alto:

var ravenWriter = Activator.CreateInstance(ravenWriterType); 

Questa è la mia domanda:
Come posso usare che come RavenWriterBase o IRavenWriter? Qualcosa di simile:

ravenWriter.Initialize(60, func); 

penso che debba essere qualcosa di simile, ma ho bisogno di specificare un tipo per IRavenWriter <> e non so ancora:

var ravenWriter = Activator.CreateInstance(ravenWriterType) as IRavenWriter<>; 

Se io il mouse sopra ravenWriter, ho successo il mio oggetto:

enter image description here

Ma ora ho bisogno di essere in grado di usarlo in modo generico. Come lo posso fare?

Aggiornamento:

ho pensato di usare la parola chiave dinamica, e questo funziona:

dynamic ravenWriter = Activator.CreateInstance(ravenWriterType); 
ravenWriter.Initialize(60); 

ho barato un po 'perché mi sono reso conto che il Func è stato lo stesso per ogni IDataEntity, così non era necessario passare come parametro a Initialize(). Tuttavia, almeno ora I può chiamare Initialize(). Ma ora che Func è lo stesso, non dovrei nemmeno aver bisogno dell'interfaccia generica.

+2

Sembra un uso improprio di farmaci generici. Quando ti stai ritrovando a dover eseguire diversi metodi in base a tipi diversi, ma i tipi sono noti solo al runtime, devi utilizzare il polimorfismo. Cosa ti stanno guadagnando i generici? Puoi eseguire i metodi così com'è senza generici in primo luogo, vale a dire con IDataEntity? – mellamokb

+1

Sono d'accordo con mellamokb. Tuttavia, se sei impostato su generici, perché non creare una classe wrapper che puoi creare un'istanza non genetica. Il wrapper dovrebbe avere un metodo che restituisce un'istanza di RavenWriterBase a seconda del tipo passato come argomento. Quindi, creare un'istanza del wrapper e chiamare il metodo con il tipo requestedd come parametro. Ciò richiederà una dichiarazione switch di grandi dimensioni, ma almeno non 200 classi separate. – dcow

+2

Crei altre espressioni che non utilizzano IDataEntity per T? O è sempre un IDataEntity? E se T è sempre IDataEntity, perché usare i generici? –

risposta

2

La mia soluzione sarebbe quella di:

  • creare un'interfaccia non generica di IRavenWriter
  • Fai IRavenWriter<T> ereditare da IRavenWriter
  • Tenere Execute e ExecutionIntervalInSeconds in IRavenWriter
  • Fai IRavenWriter hanno Func<DateTime> e l'uso quello nel tuo scrittore
  • M ove Initialize-IRavenWriter<T>
  • Utilizzare una fabbrica per inizializzare il Func a seconda del tipo e l'espressione:

Ad esempio:

public class MyDateTime 
{ 
    public DateTime This { get; set; } 
} 

public static Func<DateTime> GetFunk<T>(Expression<Func<T, DateTime>> timeOrderByFunc, T t) 
{ 
    return() => timeOrderByFunc.Compile()(t); 
} 

E si utilizza:

GetFunk<MyDateTime>(x => x.This, new MyDateTime(){This = DateTime.Now}); 
+0

Non sono sicuro di seguirlo. Come mi permette di usare ravenWriter come RavenWriterBase <> o IRavenWriter <>? –

+0

@BobHorn no. Non puoi avere una conoscenza di 'RavenWriterBase <>' lì. Invece devi usare 'IRavenWriterBase' (non generico) lì dentro.La conoscenza del tipo in fase di compilazione non esiste lì, quindi non puoi trasmettere. – Aliostad

+0

Grazie. Ho appena modificato la mia domanda per mostrare come posso chiamare Initialize() usando dynamic. Penso che dovrebbe funzionare. –

1

Non è davvero difficile trasformare il tempo di esecuzione Type in un parametro di tipo generico in fase di compilazione. Basta introdurre nuova interfaccia per la creazione/inizializzazione gli oggetti:

 

interface IRawenWriterFactory 
{ 
    object Create(); 
} 

class RawenWriterFactory<T> : IRawenWriterFactory 
{ 
    public object Create() 
    { 
    Expression<Func<IDataEntity, DateTime>> func = x => x.CicReadTime; 

    var ravenWriter = new RavenWriterBase<T>(); 
    ravenWriter.Initialize(60, func); 

    return ravenWriter; 
    } 
} 
 

Ora basta creare RawenWriterFactory con dataEntityType proprio come hai creato ravenWriter e utilizzarlo tramite interfaccia IRavenWriterFactory non generico.

Tuttavia, ci potrebbero essere soluzioni più semplici se cambierai il tuo design. Per esempio. se si attiva il metodo Initialize in costruttore, sarà possibile passare func come parametro Activator.CreateInstance e non sarà necessario utilizzare l'interfaccia generica per l'inizializzazione.