2012-03-27 10 views
6

Ad esempiocome migliorare questo metodo usando il polimorfismo + sovraccarico in modo da ridurre IS (controllo del tipo)?

BaseClass MyBase() 
{ 
    public int Add(BaseClass next) 
    { 
     if (this is InheritedA && next is InheritedA) 
      return 1; 
     else if (this is InheritedA && next is InheritedB) 
      return 2; 
     else if (this is InheritedB && next is InheritedA) 
      return 3; 
     else if (this is InheritedB && next is InheritedB) 
      return 4;  
    } 
} 

dove InheritedA e InheritedB sono le classi ereditate. In effetti, ci sono più classi ereditate e lo Add restituisce risultati diversi in base all'ordine e ai tipi del suo operando.

Sto pensando di riscriverlo usando il Polymorphism e il sovraccarico, tuttavia, diventa piuttosto complicato, devo introdurre un metodo di supporto per risolvere il tipo di entrambe le estremità.

ad es.

InheritedA myA() 
{ 
    public override int Add(BaseClass next) 
    { 
     return next.AddTo(this); 
    } 
} 

Ora devo mettere in AddToBaseClass, e sovrascrivere in classe ereditata pure.

InheritedA myA() 
{ 
    public override int AddTo(InheritedA next) { return 1; } 
    public override int AddTo(InheritedB next) { return 3; } 
} 

BaseClass myBase() 
{ 
    public abstract int Add(BaseClass next); 
    public abstract int AddTo(InheritedA next); 
    public abstract int AddTo(InheritedB next); 
} 

C'è un modo migliore di farlo?

+0

Cosa succede quando c'è una classe InheritedC? IE fa A + C = A + B? – Sign

+0

Esistono solo due valori per ogni operando (lval & rval)? – CAbbott

+0

@CAbbott Posso modificare la mia classe per obbedire a questa limitazione, quindi sì solo due valori – colinfang

risposta

1

Come è stato suggerito nei commenti, se si è in grado di assegnare un valore costante a ciascuna derivata, è possibile creare un'implementazione molto più pulita di quanto sto descrivendo qui semplicemente avendo una proprietà virtuale denominata Value o simile che è usato per l'aggiunta.

Supponendo che non sia un'opzione, è possibile considerare di pre-calcolare i risultati a livello di classe base per descrivere i valori che si assegnano per ciascuna combinazione. Questo può crollare e diventare un errore incline e noioso con l'aumentare dell'insieme di classi, quindi suggerirei di prenderlo in considerazione solo se si prevede di mantenere un set molto piccolo.

Nel mio rudimentale esempio, ho usato un dizionario per tenere il set e codificare le combinazioni. Dal tuo commento, sembra che nessuna delle regole base dell'aritmetica si applichi, quindi li ho lasciati fuori come vincoli qui. Se il valore del risultato non ha un significato reale e lo stai solo incrementando, potresti considerare di costruire il set di risultati usando il reflection per tirare le classi derivate e considerando ogni combinazione.

public class BaseClass 
{ 
    private static readonly Dictionary<int, int> addResults = new Dictionary<int, int>(); 

    static BaseClass() 
    { 
    addResults.Add(CreateKey(typeof(ChildA), typeof(ChildA)), 1); 
    addResults.Add(CreateKey(typeof(ChildA), typeof(ChildB)), 2); 
    addResults.Add(CreateKey(typeof(ChildB), typeof(ChildA)), 3); 
    addResults.Add(CreateKey(typeof(ChildB), typeof(ChildB)), 4); 
    } 

    public static int CreateKey(Type a, Type b) 
    { 
    return (String.Concat(a.Name, b.Name).GetHashCode()); 
    } 

    public int Add(BaseClass next) 
    { 
    var result = default(int); 

    if (!addResults.TryGetValue(CreateKey(this.GetType(), next.GetType()), out result)) 
    { 
     throw new ArgumentOutOfRangeException("Unknown operand combination"); 
    } 

    return result; 
    } 
} 

public class ChildA : BaseClass {} 
public class ChildB : BaseClass {} 
+0

temo che sarebbe più lento della mia versione 1? – colinfang

+0

È sempre difficile speculare sulle prestazioni, ma mi aspetto che le prestazioni siano comparabili su un set di dati rappresentativo. L'implementazione pre-elaborata richiede un po 'più di overhead per costruire il set calcolato. Ogni chiamata a "Aggiungi" dovrebbe allocare memoria per eseguire la concatenazione e il calcolo del valore hash, una volta per chiamata. L'implementazione iniziale sta facendo l'equivalente di GetType per ogni ramo del condizionale utilizzando l'operatore "is" nella clausola. A meno che tu non sia in un sistema in tempo reale, non penso che la performance sia un criterio significativo tra i due. –

8

Il modello si sta implementando si chiama doppia spedizione virtuale.

A un'unica spedizione virtuale sceglie quale metodo per chiamare sulla base del tipo runtime del ricevitore e il compilazione tipo di tempo degli argomenti. Questo è un tradizionale invio virtuale:

abstract class Animal {} 
class Tiger : Animal {} 
class Giraffe : Animal {} 
class B 
{ 
    public virtual void M(Tiger x) {} 
    public virtual void M(Animal x) {} 
} 
class D : B 
{ 
    public override void M(Tiger x) {} 
    public override void M(Animal x) {} 
} 
... 
B b = whatever; 
Animal a = new Tiger(); 
b.M(a); 

Quale metodo viene chiamato? B.M(Tiger) e D.M(Tiger) non sono scelti; li rifiutiamo in base al tempo di compilazione dell'argomento, che è Animale. Ma scegliamo se chiamare B.M(Animal) o D.M(Animal) in fase di esecuzione in base al fatto che whatever è new B() o new D().

doppie virtuale sceglie di spedizione quale metodo chiamata in base ai tipi di runtime di due cose.Se C# supporta il doppio invio virtuale, ma non lo fa, il dispatch di runtime passerebbe a B.M(Tiger) o D.M(Tiger) anche se il tipo di argomento in fase di compilazione è Animal.

C# 4 supporta tuttavia la spedizione dinamica. Se dici

dynamic b = whatever; 
dynamic a = new Tiger(); 
b.M(a); 

Poi l'analisi di M sarà fatto interamente in fase di esecuzione utilizzando i tipi di runtime di b e a. Questo è molto più lento, ma funziona.

In alternativa, se si vuole fare doppia spedizione virtuale e ottenere quanto più l'analisi fatta al momento della compilazione, come possibile, allora il modo standard per farlo è quello di implementare il modello Visitor, che si può guardare sul internet facilmente.

+0

Davvero bello sapere tutto questo. Hai detto "tipo di runtime del ricevitore e il tipo di tempo di compilazione degli argomenti" .. ma perché dici che il tipo di runtime di "a" non è Animal e invece è Tiger? So di poter eseguire un typecast a Tiger, ma il tipo di "a" prima di typecasting è ancora Animal. no ? – Dhananjay

+1

@dnkulkarni, il tipo di runtime di qualcosa è il tipo effettivo in fase di esecuzione. E il tipo effettivo qui è 'Tiger', perché è ciò che è effettivamente memorizzato in' a' (o meglio, un riferimento a 'Tiger' è). – svick

+1

@dnkulkarni: il tipo di * la variabile * è Animale. Questo è il "tipo di tempo di compilazione" dell'espressione. Il "tipo di runtime" dell'espressione è il tipo di cosa che è effettivamente memorizzata nella variabile. –

Problemi correlati