2012-03-27 15 views
6

Por ejemplocómo mejorar este método usando polimorfismo + sobrecarga para reducir IS (verificación de 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;  
    } 
} 

donde InheritedA, y InheritedB son sus clases heredadas. De hecho, hay más clases heredadas y el Add arroja resultados diferentes según el orden y los tipos de su operando.

Estoy pensando en reescribirlo usando Polimorfismo y sobrecarga, sin embargo, se vuelve bastante complicado, tengo que introducir un método de ayuda para resolver el tipo de cualquiera de los extremos.

p. Ej.

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

Ahora tengo que poner en AddToBaseClass y anularlo en la clase heredada también.

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

¿Hay una mejor manera de hacerlo?

+0

¿Qué sucede una vez que hay una clase InheritedC? IE hace A + C = A + B? – Sign

+0

¿Hay solo dos valores para cada operando (lval & rval)? – CAbbott

+0

@CAbbott Puedo modificar mi clase para obedecer esta restricción, así que sí solo dos valores – colinfang

Respuesta

1

Como se sugirió en los comentarios, si puede asignar un valor constante a cada derivada, entonces puede construir una implementación mucho más limpia de la que estoy describiendo aquí simplemente teniendo una propiedad virtual llamada Value o similar que es solía para la adición.

Suponiendo que no es una opción, puede considerar precomputar los resultados en el nivel de clase base para describir los valores que está asignando para cada combinación. Esto puede descomponerse y volverse propenso a errores y tedioso a medida que el conjunto de clases crece, por lo que sugiero solo considerar esto si espera que se mantenga un conjunto muy pequeño.

En mi ejemplo rudimentario, utilicé un diccionario para mantener el conjunto y codifiqué las combinaciones. De su comentario, parece que ninguna de las reglas básicas de la aritmética se aplica, por lo que las he omitido como restricciones aquí. Si el valor del resultado no tiene un significado real y simplemente lo está incrementando, podría considerar construir el conjunto de resultados utilizando la reflexión para extraer las clases derivadas y considerar cada combinación.

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

me temo que sería más lento que mi versión 1? – colinfang

+0

Siempre es difícil especular sobre el rendimiento, pero espero que el rendimiento sea comparable a un conjunto de datos representativos. La implementación pre calculada requiere un poco más de sobrecarga para construir el conjunto calculado. Cada llamada a "Agregar" necesitaría asignar memoria para realizar el cálculo del valor de concatenación y hash, una vez por llamada. La implementación inicial está haciendo el equivalente a GetType para cada rama del condicional utilizando el operador "es" en la cláusula. A menos que esté en un sistema en tiempo real, no creo que el rendimiento sea un criterio significativo entre los dos. –

8

El patrón va a implementar se llama despacho virtual de doble.

A individuales de despacho virtual elige qué método a llamar sobre la base del tipo de tiempo de ejecución del receptor y el tiempo de compilación tipo de los argumentos. Este es un despacho virtual tradicional:

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

¿Qué método se llama? B.M(Tiger) y D.M(Tiger) no son elegidos; los rechazamos basados ​​en tiempo de compilación tipo del argumento, que es Animal. Pero elegimos si llamar a B.M(Animal) o D.M(Animal) en tiempo de ejecución según si whatever es new B() o new D().

dobles virtuales elige despacho el método a llamadas en base a los tipos de tiempo de ejecución de dos cosas .Si C# admite el envío virtual doble, que no es así, el envío en tiempo de ejecución iría a B.M(Tiger) o D.M(Tiger), aunque el tipo de argumento en tiempo de compilación es Animal.

C# 4 sin embargo admite envío dinámico. Si usted dice

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

A continuación, se llevará a cabo el análisis de M por completo en tiempo de ejecución utilizando los tipos de tiempo de ejecución de b y a. Esto es significativamente más lento, pero funciona.

Alternativamente, si usted quiere hacer despacho virtual de doble y obtener la mayor cantidad de análisis realiza en tiempo de compilación como sea posible después de la forma estándar de hacerlo es poner en práctica el modelo de Visitante, que se puede consultar en la Internet fácilmente.

+0

Realmente es bueno saber todo esto. Dijiste "tipo de tiempo de ejecución del receptor y el tipo de tiempo de compilación de los argumentos" ... pero ¿por qué dices que el tipo de tiempo de ejecución de "a" no es Animal y en su lugar es Tiger? Sé que puedo encasillarlo a Tiger, pero el tipo de "a" antes de encasillarme sigue siendo Animal. no ? – Dhananjay

+1

@dnkulkarni, el tipo de tiempo de ejecución de algo es el tipo real en tiempo de ejecución. Y el tipo real aquí es 'Tiger', porque eso es lo que realmente está almacenado en' a' (o más bien, una referencia a 'Tiger' es). – svick

+1

@dnkulkarni: El tipo de * la variable * es Animal. Ese es el "tipo de tiempo de compilación" de la expresión. El "tipo de tiempo de ejecución" de la expresión es el tipo de lo que realmente está almacenado en la variable. –

Cuestiones relacionadas