2009-04-17 18 views
12

Ho una classe che viene utilizzata in un'applicazione client e in un'applicazione server. Nell'applicazione server, aggiungo alcune funzionalità ai metodi di estensione della classe attraverso. Funziona alla grande. Ora voglio ancora un po ':Metodi di estensione virtuale?

La mia classe (B) eredita da un'altra classe (A).

Vorrei aggiungere una funzione virtuale ad A (diciamo Execute()), quindi implementare quella funzione in B. Ma solo nel server. Il metodo Execute() dovrebbe fare cose che è possibile fare solo sul server, usando tipi che solo il server conosce.

Ci sono molti tipi che ereditano da A proprio come B, e mi piacerebbe implementare Execute() per ognuno di essi.

Speravo di aggiungere un metodo di estensione virtuale ad A, ma quell'idea non sembra volare. Sto cercando il modo più elegante per risolvere questo problema, con o senza metodi di estensione.

risposta

4

No, non ci sono cose come i metodi di estensione virtuale. È possibile utilizzare l'overloading, ma questo non supporta il polimorfismo. Sembra che si potrebbe desiderare di guardare a qualcosa come l'iniezione di dipendenza (etc) per avere codice diversi (dipendenze) aggiunto in ambienti diversi - e utilizzarlo in normali metodi virtuali:

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

Poi utilizzare un quadro DI fornire un'implementazione ISomeUtility specifica del server a B in fase di esecuzione. Si può fare la stessa cosa con un static registro centrale (IOC, ma nessuna DI):

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

(in cui si avrebbe bisogno di scrivere Registry ecc; più sul server, registrare l'implementazione ISomeUtility)

+0

Grazie Marc. Implementerò qualcosa di simile. È un po 'più complicato per me, perché serializzo queste classi e le mando sul filo dal server al client e viceversa. Quindi il DI tradizionale potrebbe essere un po 'complicato, ma suppongo di poter implementare un equivalente serveride di classe B, (probabilmente ereditando da B), e quando il client invia al server un'istanza di B, dovrò sostituirlo con un nuova istanza di ServerB. – Lucas

+0

L'approccio del registro funziona correttamente con la serializzazione. Io uso questo approccio per fare la condivisione degli assembly con oggetti WCF ... –

0

Virtual implica l'ereditarietà in un modo OOP ei metodi di estensione sono "solo" metodi statici che attraverso un po 'di zucchero sintattico il compilatore consente di fingere di chiamare un'istanza del tipo del suo primo parametro. Quindi no, i metodi di estensione virtuali sono fuori questione.

Controlla la risposta di Marc Gravell per una possibile soluzione al tuo problema.

0

È possibile implementare un registro di servizio. Esempio (lato server):

static IDictionary<Type, IService> serviceRegister; 

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

Quello che vi serve sono piuttosto servizi nel server, che implementano funzionalità lato server, invece di metodi di estensione. Non metterei molta logica nei metodi di estensione.

0

Lasciami controllare: si ha una gerarchia di classe ereditata da A, presumibilmente strutturata in base al proprio dominio aziendale. Quindi si desidera aggiungere comportamenti a seconda di dove vengono eseguite le classi. Finora hai usato i metodi di estensione, ma ora trovi che non puoi farli variare con la tua gerarchia di classi. Quali tipi di comportamenti stai collegando al server?

Se sono cose come la gestione delle transazioni e la sicurezza, le politiche implementate tramite l'iniezione della dipendenza secondo il suggerimento di Marc dovrebbero funzionare bene. Potresti anche prendere in considerazione l'implementazione di Strategy pattern tramite delegati e lambda, per una versione più limitata di DI. Tuttavia, ciò che non è chiaro è come il codice client attualmente usi le tue classi e i loro metodi di estensione sul server. Quanto sono dipendenti le altre classi su come si aggiunge la funzionalità lato server?Sono solo le classi sul lato server che attualmente si aspettano di trovare i metodi di estensione?

In ogni caso, sembra che avrete bisogno di un'attenta strategia di testabilità e test di verifica poiché state introducendo la variazione lungo due dimensioni simultanee (gerarchia di ereditarietà, ambiente di esecuzione). Mi stai avvalendo dei test unitari, mi fido? Verifica che qualsiasi soluzione tu scelga (ad es. Attraverso la configurazione) interagisca bene con il testing e il mocking.

2

Vorrei suggerire qualcosa come il seguente. Questo codice potrebbe essere migliorato aggiungendo il supporto per il rilevamento di tipi di gerarchia di classi intermedie che non dispongono di un mapping di distribuzione e che chiamano il metodo di distribuzione più vicino in base alla gerarchia di runtime. Potrebbe anche essere migliorato utilizzando la riflessione per rilevare il sovraccarico di ExecuteInteral() e aggiungerli automaticamente alla mappa di spedizione.

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

È possibile utilizzare la nuova funzionalità di tipo dinamico per evitare di dover costruire un registro dei tipi di metodi:

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

hai detto, mi piace questo esempio, ma avresti potuto mettere il tuo punto molto più sipler. –

Problemi correlati