2012-04-13 12 views
5

He estado trabajando en un problema durante un par de horas y creo que estoy cerca. Estoy trabajando en una aplicación donde podríamos tener 50-100 tipos que funcionan de la misma manera. Así que en lugar de crear clases 50-100, traté de hacer que sea genérico y esto es lo que tengo:Crear tipo genérico con interfaz genérica en tiempo de ejecución

Ésta es la clase base:

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

y esta es la interfaz:

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

Y así es como lo estoy 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) 
    } 
} 

estoy atascado en esta línea desde arriba:

var ravenWriter = Activator.CreateInstance(ravenWriterType); 

Esta es mi pregunta:
¿Cómo puedo usar eso como RavenWriterBase o IRavenWriter? Algo así como:

ravenWriter.Initialize(60, func); 

Creo que tiene que ser algo como esto, pero necesito especificar un tipo de IRavenWriter <> y no sé todavía:

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

Si yo ciernen sobre ravenWriter, tengo éxito mi objeto:

enter image description here

Pero ahora tengo que ser capaz de utilizarlo de una manera genérica. ¿Cómo puedo hacer eso?

Actualización:

me acaba de ocurrir usando la palabra clave dinámica, y esto funciona:

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

he engañado un poco porque me di cuenta de que la Func era el mismo para cada IDataEntity, por lo eso no era necesario pasar como parámetro a Initialize(). Sin embargo, al menos ahora I puede llamar a Initialize(). Pero ahora que el Func es el mismo, tampoco debería necesitar la interfaz genérica.

+2

Parece que el uso inadecuado de genéricos. Cuando te encuentras necesitando ejecutar diferentes métodos basados ​​en diferentes tipos, pero los tipos son conocidos solo en tiempo de ejecución, quieres hacer uso del polimorfismo. ¿Qué te está ganando los genéricos? ¿Puede ejecutar los métodos tal como están sin genéricos en primer lugar, es decir, con IDataEntity? – mellamokb

+1

Estoy de acuerdo con mellamokb. Sin embargo, si tiene genéricos, ¿por qué no crear una clase contenedora que pueda crear instancias no genáricamente? El contenedor debería tener un método que devuelva una instancia de RavenWriterBase según el tipo pasado como argumento. A continuación, crea una instancia del contenedor y llama al método con el tipo solicitado como parámetro. Esto requerirá una declaración de interruptor grande, pero al menos no 200 clases separadas. – dcow

+2

¿Creas otras expresiones que no usan IDataEntity para T? ¿O es T siempre una IDataEntity? Y si T es siempre IDataEntity, ¿por qué usar genéricos? –

Respuesta

2

Mi solución sería:

  • crear una interfaz no genérico de IRavenWriter
  • Hacer IRavenWriter<T> Heredar del IRavenWriter
  • Mantenga Execute y ExecutionIntervalInSeconds en IRavenWriter
  • Hacer IRavenWriter tienen Func<DateTime> y el uso que en su escritor
  • M ove Initialize a IRavenWriter<T>
  • Utilice una fábrica para inicializar el Func acuerdo con el tipo y la expresión:

Por ejemplo:

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

Y que utilice:

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

No estoy seguro de seguir. ¿Cómo me permite esto usar RavenWriter como RavenWriterBase <> o IRavenWriter <>? –

+0

@BobHorn no es así. No puede tener conocimiento de 'RavenWriterBase <>' allí. En su lugar, debe usar 'IRavenWriterBase' (no genérico) allí.El conocimiento en tiempo de compilación del tipo no existe allí, por lo que no puede transmitir. – Aliostad

+0

Gracias. Acabo de editar mi pregunta para mostrar cómo puedo llamar a Initialize() usando dynamic. Creo que debería funcionar –

1

No es realmente difícil convertir el tiempo de ejecución Type en el parámetro de tipo genérico en tiempo de compilación. Sólo introducir una nueva interfaz para crear/inicializar los objetos:

 

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

Ahora acaba de crear RawenWriterFactory con dataEntityType al igual que ha creado ravenWriter y utilizarlo a través de interfaz IRavenWriterFactory no genérico.

Sin embargo, podría haber soluciones más simples si cambia su diseño. P.ej. si conviertes el método Initialize en constructor, podrás pasar func como parámetro Activator.CreateInstance y no necesitarás usar una interfaz genérica para la inicialización.

Cuestiones relacionadas