2009-04-17 14 views
12

Tengo una clase que se usa en una aplicación cliente y en una aplicación de servidor. En la aplicación de servidor, agrego algunas funcionalidades a los métodos de extensión de clase. Funciona genial. Ahora quiero un poco más:¿Métodos de extensión virtual?

Mi clase (B) hereda de otra clase (A).

Me gustaría adjuntar una función virtual a A (digamos Execute()), y luego implementar esa función en B. Pero solo en el servidor. El método Execute() necesitaría hacer cosas que solo es posible hacer en el servidor, usando tipos que solo el servidor conoce.

Hay muchos tipos que heredan de A como B, y me gustaría implementar Execute() para cada uno de ellos.

Esperaba que pudiera agregar un método de extensión virtual a A, pero esa idea no parece volar. Estoy buscando la forma más elegante de resolver este problema, con o sin métodos de extensión.

Respuesta

4

No, no existen métodos de extensión virtual. Podría usar la sobrecarga, pero eso no es compatible con el polimorfismo. Parece que usted puede ser que desee ver en algo así como la inyección de dependencias (etc) para tener códigos diferentes (dependencias) añadido en diferentes entornos - y utilizarlo en los métodos virtuales regulares:

class B { 
    public B(ISomeUtility util) { 
     // store util 
    } 
    public override void Execute() { 
     if(util != null) util.Foo(); 
    } 
} 

A continuación, utilice un marco DI para proporcionar una implementación de ISomeUtility específica del servidor a B en tiempo de ejecución. Puede hacer lo mismo con un static registro central (COI, pero sin DI):

override void Execute() { 
     ISomeUtility util = Registry.Get<ISomeUtility>(); 
     if(util != null) util.Foo(); 
    } 

(donde había necesidad de escribir Registry etc; además en el servidor, el registro de la aplicación ISomeUtility)

+0

Gracias Marc. Implementaré algo como esto. Para mí es un poco más complicado, ya que serializo estas clases y las envío por cable desde el servidor al cliente y viceversa. Así que DI tradicional puede ser un poco complicado, pero creo que puedo implementar un equivalente en el servidor de clase B (probablemente heredando de B) y cuando el cliente envía al servidor una instancia de B, tendré que reemplazarlo por un nueva instancia de ServerB. – Lucas

+0

El enfoque de registro funciona bien con serialización. Utilizo ese enfoque para compartir ensamblajes con objetos WCF ... –

0

Virtual implica herencia de una manera OOP y los métodos de extensión son "solo" métodos estáticos que a través de un bit un azúcar sintáctico el compilador le permite simular invocar una instancia del tipo de su primer parámetro. Entonces, no, los métodos de extensión virtual están fuera de discusión.

Consulte la respuesta de Marc Gravell para una posible solución a su problema.

0

Puede implementar un registro de servicio. Ejemplo (lado del servidor):

static IDictionary<Type, IService> serviceRegister; 

public void ServerMethod(IBusinessType object) 
{ 
    serviceRegister[obect.GetType()].Execute(object); 
} 

Lo que necesitamos son más bien los servicios en el servidor, que implementan la funcionalidad del lado del servidor, en lugar de los métodos de extensión. No pondría mucha lógica en los métodos de extensión.

0

Déjame verificar: tienes una jerarquía de clases que hereda de A, probablemente estructurada de acuerdo con tu dominio comercial. Luego desea agregar comportamientos dependiendo de dónde se ejecutan las clases. Hasta ahora has usado métodos de extensión, pero ahora descubres que no puedes hacer que varíen con tu jerarquía de clases. ¿Qué tipo de comportamientos está adjuntando en el servidor?

Si se trata de cosas como la gestión de transacciones y la seguridad, las políticas implementadas a través de la inyección de dependencia à la sugerencia de Marc deberían funcionar bien. También podría considerar implementar el Strategy pattern a través de delegados y lambdas, para obtener una versión más limitada de DI. Sin embargo, lo que no está claro es cómo el código de cliente usa actualmente tus clases y sus métodos de extensión en el servidor. ¿De qué manera dependen otras clases de cómo se agrega la funcionalidad del lado del servidor?¿Están las clases del lado del servidor que esperan encontrar los métodos de extensión?

En cualquier caso, parece que va a necesitar una estrategia de diseño y prueba cuidadosa de prueba ya que está introduciendo la variación a lo largo de dos dimensiones simultáneas (jerarquía de herencia, entorno de ejecución). Estás usando pruebas unitarias, ¿confío? Compruebe que la solución que elija (por ejemplo, DI a través de la configuración) interactúa bien con las pruebas y las burlas.

2

Sugeriría algo como lo siguiente. Este código podría mejorarse agregando soporte para detectar tipos de jerarquía de clase intermedia que no tienen una asignación de despacho y llamando al método de despacho más cercano en función de la jerarquía de tiempo de ejecución. También podría mejorarse utilizando la reflexión para detectar la sobrecarga de ExecuteInteral() y agregarlos automáticamente al mapa de despacho.

using System; 
using System.Collections.Generic; 

namespace LanguageTests2 
{ 
    public class A { } 

    public class B : A {} 

    public class C : B {} 

    public static class VirtualExtensionMethods 
    { 
     private static readonly IDictionary<Type,Action<A>> _dispatchMap 
      = new Dictionary<Type, Action<A>>(); 

     static VirtualExtensionMethods() 
     { 
      _dispatchMap[typeof(A)] = x => ExecuteInternal((A)x); 
      _dispatchMap[typeof(B)] = x => ExecuteInternal((B)x); 
      _dispatchMap[typeof(C)] = x => ExecuteInternal((C)x); 
     } 

     public static void Execute(this A instance) 
     { 
      _dispatchMap[instance.GetType()](instance); 
     } 

     private static void ExecuteInternal(A instance) 
     { 
      Console.WriteLine("\nCalled ToString() on: " + instance); 
     } 

     private static void ExecuteInternal(B instance) 
     { 
      Console.WriteLine("\nCalled ToString() on: " + instance); 
     } 

     private static void ExecuteInternal(C instance) 
     { 
      Console.WriteLine("\nCalled ToString() on: " + instance); 
     } 
    } 

    public class VirtualExtensionsTest 
    { 
     public static void Main() 
     { 
      var instanceA = new A(); 
      var instanceB = new B(); 
      var instanceC = new C(); 

      instanceA.Execute(); 
      instanceB.Execute(); 
      instanceC.Execute(); 
     } 
    } 
} 
3

Puede usar la nueva funcionalidad de tipo dinámico para evitar tener que construir un registro de los tipos de métodos:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using visitor.Extension; 

namespace visitor 
{ 
    namespace Extension 
    { 
     static class Extension 
     { 
      public static void RunVisitor(this IThing thing, IThingOperation thingOperation) 
      { 
       thingOperation.Visit((dynamic)thing); 
      } 

      public static ITransformedThing GetTransformedThing(this IThing thing, int arg) 
      { 
       var x = new GetTransformedThing {Arg = arg}; 
       thing.RunVisitor(x); 
       return x.Result; 
      } 
     } 
    } 

    interface IThingOperation 
    { 
     void Visit(IThing iThing); 
     void Visit(AThing aThing); 
     void Visit(BThing bThing); 
     void Visit(CThing cThing); 
     void Visit(DThing dThing); 
    } 

    interface ITransformedThing { } 

    class ATransformedThing : ITransformedThing { public ATransformedThing(AThing aThing, int arg) { } } 
    class BTransformedThing : ITransformedThing { public BTransformedThing(BThing bThing, int arg) { } } 
    class CTransformedThing : ITransformedThing { public CTransformedThing(CThing cThing, int arg) { } } 
    class DTransformedThing : ITransformedThing { public DTransformedThing(DThing dThing, int arg) { } } 

    class GetTransformedThing : IThingOperation 
    { 
     public int Arg { get; set; } 

     public ITransformedThing Result { get; private set; } 

     public void Visit(IThing iThing) { Result = null; } 
     public void Visit(AThing aThing) { Result = new ATransformedThing(aThing, Arg); } 
     public void Visit(BThing bThing) { Result = new BTransformedThing(bThing, Arg); } 
     public void Visit(CThing cThing) { Result = new CTransformedThing(cThing, Arg); } 
     public void Visit(DThing dThing) { Result = new DTransformedThing(dThing, Arg); } 
    } 

    interface IThing {} 
    class Thing : IThing {} 
    class AThing : Thing {} 
    class BThing : Thing {} 
    class CThing : Thing {} 
    class DThing : Thing {} 
    class EThing : Thing { } 

    class Program 
    { 
     static void Main(string[] args) 
     { 
      var things = new List<IThing> { new AThing(), new BThing(), new CThing(), new DThing(), new EThing() }; 
      var transformedThings = things.Select(thing => thing.GetTransformedThing(4)).Where(transformedThing => transformedThing != null).ToList(); 
      foreach (var transformedThing in transformedThings) 
      { 
       Console.WriteLine(transformedThing.GetType().ToString()); 
      } 
     } 
    } 
} 
+1

, konw, me gusta este ejemplo, pero podrías haber puesto tu punto mucho sipler. –

Cuestiones relacionadas