2010-08-16 13 views
7

Quiero hacer que este pase de prueba, ¿alguien tuvo una idea de cómo hacerlo?Hacer que el objeto implemente dinámicamente una interfaz en el código

public class Something 
{ 
    public string Name {get; set} 
} 

public interface IWithId 
{ 
    public Guid Id {get; set} 
} 

public class IdExtender 
{ 
    public static Object Extend(object toExtend) 
    { 
     ...? 
    } 
} 

public class Tests 
{ 
    [Test] 
    public void Should_extend_any_object() 
    { 
     var thing = new Something { Name = "Hello World!"}; 
     var extended = IdExtender.Extend(thing); 
     Assert.IsTrue(extended is IWithId); 
     Assert.IsTrue(extended.Id is Guid); 
     Assert.IsTrue(extened.Name == "Hello World!"); 
    } 
} 

supongo que algo como esto se podría hacer con Proxy castillo dinámico, Linfu, etc ... pero ¿cómo?

+0

Si hay una manera de hacer esto, tengo curiosidad ... –

+0

Respondo la pregunta, ya lo he hecho antes, pero no quiero ser superado por algunas frases sencillas y una referencia o dos a la documentación si voy a pasar por la molestia de romper el estudio visual abierto y armar la solución ... así que esperaré un poco, y si no hay una buena respuesta en un par de horas, enviaré un responder. – Steve

+0

¿Extendería esto la instancia o todo el tipo de algo? –

Respuesta

3

Usando Castillo DP (obviamente)

Bien - usted tendrá que crear un nuevo objeto de volver ya que no puede tener un tipo existente ganar una nueva interfaz como su programa se está ejecutando.

Para hacer esto, necesitará crear un proxy y luego replicar el estado de su objeto preexistente en el proxy. DP no hace eso OOTB. En v2.5, podría usar el nuevo proxy de clase con destino, pero eso solo funcionaría si todas las propiedades del tipo fueran virtuales.

De todos modos. Puede hacer que el nuevo tipo gane la interfaz IWithId mezclando el proxy con un objeto existente que implemente la propiedad. Luego, las llamadas a los miembros de la interfaz se reenviarán al objeto.

Como alternativa, puede proporcionarlo como una interfaz adicional para implementar y tener un interceptor que complete el rol de implementador.

+0

Suena interesante: ¿no tienes una muestra de código en alguna parte? – Jan

+0

Podría producir uno ... supongo que –

+0

significa que lo has entendido y no quieres que yo cree una muestra para ti? –

1

¿Por qué no utilizar un mocking framework like Moq o RhinoMocks. Le permiten implementar interfaces dinámicamente sin la dificultad de crear un proxy directamente.

Con Moq se podría escribir:

var mock = new Mock<IWithId>(); // mock the interface 
mock.Setup(foo => foo.Name).Returns("Hello World"); // setup a property 

Aparte de eso, usted tendría que hacer un trabajo complicado para conseguir lo que desea. Por lo que yo sé, no puede agregar método o propiedades a una clase existente en tiempo de ejecución. Podría devolver una instancia de un nuevo objeto que hereda dinámicamente del tipo pasado. Castle definitivamente permite esto, pero el código necesario no es especialmente elegante o simple.

+1

Hm , pero ¿pueden agregarlo a un objeto existente? Eso sería un abuso interesante :) – Jan

+0

I @ illdev: Con Moq no sería necesario agregarlo a un objeto existente; simplemente establecería el simulacro para que también tenga la propiedad dada. No se pueden agregar dinámicamente métodos o propiedades a ninguna clase existente, de todos modos. Podrías quizás usar objetos dinámicos de .NET 4.0, pero eso requeriría que la persona que llama utilizara 'dynamic' también. – LBushkin

+0

El afiche no dedujo que su tipo tenía que cambiar dinámicamente. El tipo de devolución de su método "convertidor" es "objeto". Esto es definitivamente factible en .NET 3.5 y en versiones anteriores – Steve

0

Dejando a un lado la cuestión de cómo adjuntar dinámicamente una propiedad o interfaz, parece que lo que está intentando hacer es aumentar las clases existentes con datos adicionales. Una solución muy típica a este problema es usar un Dictionary<Something, SomethingExtra> y almacenarlo en alguna clase de servicio que mantenga la asignación. Ahora, cuando necesite acceder a SomethingExtra, simplemente solicite a la clase de servicio la información asociada.

Ventajas:

  1. La aplicación es fácil de entender y de mantener que una solución mediante la reflexión y la generación de proxy dinámico.

  2. Si necesita derivar del tipo extendido, la clase no se puede sellar. Asociar información externamente funciona bien con clases selladas.

  3. Puede asociar información sin tener que ser responsable de la construcción del objeto aumentado. Puede tomar instancias creadas en cualquier lugar y asociar la nueva información.

Desventajas:

  1. que necesita inyectarse la instancia de servicio que mantiene la asignación. Puedes hacerlo a través de un framework IoC si estás usando uno, inyección manual (generalmente pasa a través de un constructor) o, si no hay otra alternativa, a través de un acceso directo estático de Singleton.

  2. Si tiene un gran número de instancias y se están creando/eliminando rápidamente, la sobrecarga del diccionario podría ser notable. Sospecho que necesitarías una carga muy pesada antes de que la sobrecarga se note en comparación con una implementación de proxy.

+0

Bienvenido al mundo no administrado. El desarrollador debería estar SEGURO de que eliminaron todas las entradas de objetos del diccionario cuando terminaron con ellas, para, esencialmente, liberar el objeto (liberarlo para la recolección de basura). – Steve

+0

@Steve, buen punto, eso debería ir en la lista de Desventajas. Esta es básicamente la forma en que las Propiedades de dependencia adjunta funcionan en WPF, así que tendré que investigar un poco para ver cómo MS resuelve este problema. –

+0

@Steve, no pude encontrar ninguna información sobre cómo la MS resuelve el problema, pero se puede resolver usando referencias débiles y purgando periódicamente el diccionario de registros a instancias que se han recopilado. Curiosamente, esta es la primera vez que encuentro un buen caso de uso para referencias débiles. –

3

Por ahora voy con Linfu así:

public class IdExtender 
{ 
    public static Object Extend(object toExtend) 
    { 
     var dyn = new DynamicObject(toExtend); 
     dyn.MixWith(new WithId { 
           Id = Guid.New() 
           }); 
     var extended = dyn.CreateDuck<IWithId>(returnValue.GetType().GetInterfaces()); 
     return extended; 
    } 
} 
+0

¡Esto es tan genial en niveles de mayo! – Jan

+0

Encontrado en google - No se puede crear una instancia de DynamicObject solo derivado de –

1

De hecho, me han contestado a la semana pasada similar question. También he escrito una pequeña biblioteca que hace esto. Está disponible en mi blog, que estoy enchufado descaradamente.

Lo que está buscando es casi posible con Castle Dynamic Proxy. La única restricción es que la instancia existente debe implementar una interfaz y todas las propiedades/métodos que le interesan están disponibles a través de esa interfaz.

public static TIntf CreateMixinWithTarget<TIntf>(TIntf target, params object[] instances) where TIntf : class{ 

    ProxyGenerator generator = new ProxyGenerator(); 
    ProxyGenerationOptions options = new ProxyGenerationOptions(); 

    instances.ToList().ForEach(obj => options.AddMixinInstance(obj)); 

    return generator.CreateInterfaceProxyWithTarget <TIntf>(target, options); 
} 

[Test] 
public void Should_extend_any_object() 
{ 
    var thing = new Something { Name = "Hello World!"}; 
    var extended = CreateMixinWithTarget<ISomething>(thing, new WithId(), new GuidImpl()); 
    Assert.IsTrue(extended is IWithId); 
    Assert.IsTrue(extended.Id is Guid); 
    Assert.IsTrue(extened.Name == "Hello World!"); 
} 
+0

Eso es mala suerte para mí. En mi caso, las instancias no implementan ninguna interfaz ... cuando dices, deberían ... ¿cómo tiene sentido? ¿Eso no impone que la interfaz también se implemente? De todos modos, si compara la versión de Linux a continuación, creo que es mucho más elegante ... – Jan

+0

Esta es la restricción de Proxy dinámico y tiene que ver con la forma en que crea proxies. La única forma de crear un proxy basado en una instancia existente es llamar a CreateInterfaceProxyWithTarget, pero la advertencia es que la instancia debe implementar una interfaz especificada por el tipo param. Y estoy de acuerdo, la versión de Linux es mucho más limpia. –

Cuestiones relacionadas