2012-01-20 7 views
6

Estoy decodificando mensajes de comunicaciones de una secuencia binaria. Creo objetos de mensaje de diferentes tipos según los mensajes que hayan llegado. Todos derivan de un tipo base CommsMessage. Todo bien y elegante.Costo de rendimiento de las comparaciones de tipos

En otra parte de mi código necesito reaccionar a estos mensajes, así que necesito saber qué tipo de mensaje es.

Actualmente estoy haciendo:

void ProcessIncomingMessage(CommsMessage msg) 
{ 
    if (msg is MessageType1) 
     return ProcessMessageType1(msg as MessageType1); 

    if (msg is MessageType2) 
     return ProcessMessageType2(msg as MessageType2); 

    //etc 
} 

Me pregunto lo que el costo de rendimiento de la comparación de este tipo son, y si debería incluir una propiedad MessageType en la clase base en su lugar. Entonces podría hacer:

void ProcessIncomingMessage(CommsMessage msg) 
{ 
    switch (msg.MessageType) 
    { 
     case MessageType.Type1: return ProcessMessageType1(msg as MessageType1); 
     case MessageType.Type2: return ProcessMessageType2(msg as MessageType2); 

     //etc 
    } 
} 

Sí, esta es la optimización prematura, y probablemente estoy preocuparse por detalles insignificantes, pero yo soy el tipo de codificador que le gusta saber lo que está pasando debajo de las mantas y así fue preguntándose las diferencias de rendimiento entre los dos. Supongo que tengo un prejuicio contra las comparaciones de tipos de mi fondo de C++ donde RTTI introdujo una sobrecarga, y me preguntaba si .Net tenía alguna similitud.

+1

posible duplicado de [C# 'es' el rendimiento del operador] (http://stackoverflow.com/questions/686412/c-sharp-is-operator-performance) –

Respuesta

7

¿Ha considerado eliminar los moldes tipo?

Supongo que ha considerado que poner el método virtual en el tipo Message mismo rompería una abstracción de capas (por ejemplo, podría querer una separación limpia del procesamiento del mensaje del mensaje mismo). Tal vez considere el visitor pattern. Esto te permitirá separar la clase Message del procesamiento del Message.

Si tiene algo de esta estructura.

abstract class CommsMessage {} 
class Message1 : CommsMessage {} 
class Message2 : CommsMessage {} 

Se podría refactorizar a

abstract class CommsMessage 
{ 
    public abstract void Visit(CommsMessageVisitor v); 
} 

class Message1 : CommsMessage 
{ 
    public void Visit(CommsMessageVisitor v) { v.Accept(this); } 
} 

class Message2 : CommsMessage 
{ 
    public void Visit(CommsMessageVisitor v) { v.Accept(this); } 
} 

interface CommsMessageVisitor 
{ 
    void Accept(Message1 msg1); 
    void Accept(Message1 msg2); 
} 

En este punto, usted ha eliminado el tipo-moldes. Ahora puede volver a escribir el código como

void ProcessIncomingMessage(CommsMessage msg) 
{ 
    new MyVisitor().Visit(msg); 
} 

class MyVisitor : CommsMessageVisitor 
{ 
    void Accept(Message1 msg1) { ProcessMessageType1(msg1); } 
    void Accept(Message1 msg2) { ProcessMessageType2(msg2); } 
} 

Por supuesto que puede haber razones por las que no se puede hacer esto, pero siempre es más agradable para evitar conversiones de tipo si se puede!

+1

1 Para su explicación/asunción de por qué vistor podría ser necesario aquí - de lo contrario parecería excesivo ;-) –

+0

Estoy de acuerdo, definitivamente exagerado a menos que ese sea el caso! Sin embargo, parece ser un caso bastante común. Tiene algunos objetos tontos (POCO/entidades) que solo quiere transferir a lugares (tal vez compartidos entre el cliente/servidor o simplemente en diferentes niveles de abstracción). A menudo desea un procesamiento diferente de la misma, pero exponerlo en métodos virtuales expone demasiado. El patrón de visitante es increíble para mover la implementación de estos métodos. –

+0

Sí, y dados los nombres de los tipos y métodos involucrados, el suyo es una suposición razonable. –

2

Tenga en cuenta que su código no es sintácticamente válido, ya que los tipos de devolución son void, pero de todos modos.

Bueno, no estoy muy seguro sobre la diferencia de rendimiento de las dos alternativas que muestra. Sin embargo, al menos FxCop haría "suggest" el siguiente lugar de su primera solución:

void ProcessIncomingMessage(CommsMessage msg) 
{ 
    MessageType1 msg1 = msg as MessageType1; 

    if (msg1 != null) 
    { 
     ProcessMessageType1(msg1); 
     return; 
    } 

    MessageType2 msg2 = msg as MessageType2; 

    if (msg2 != null) 
    { 
     ProcessMessageType2(msg2); 
     return; 
    } 


    //etc 
} 

Por supuesto, hay otras cuestiones que participan aquí, como el mantenimiento, la comprensión, etc. Posiblemente sería mejor para proporcionar un " virtual void ProcessMessage() "en su clase" CommsMessage ", que sobrescribe para cada" MessageType ". Entonces deja que el CLR trabaje para ti.

public class CommsMessage 
{ 
    public virtual void ProcessMessage() 
    { 
     // Common stuff. 
    } 
} 

public class MessageType1 : CommsMessage 
{ 
    public override void ProcessMessage() 
    { 
     base.ProcessMessage(); 
     // type 1 specific stuff. 
    } 
} 

// ... 

void ProcessIncomingMessage(CommsMessage msg) 
{ 
    msg.ProcessMessage(); 
} 

Podría decirse, puede llamar directamente msg.ProcessMessage(), donde ahora se llama ProcessIncomingMessage, si no hay nada más ahí que hacer.

+1

+1 de mí - 'as' seguido de null check outperforms' is' then 'as' –

1

Para añadir a las excelentes respuestas anteriores:

En perfiles de rendimiento Me he dado cuenta que el uso de is seguido por as realidad como resultado un rendimiento más bajo que un solo as seguido por cheque nulo. No espere que el compilador optimice nada automáticamente. Tiene razón al suponer que en el código de mensajería (o en las secciones críticas de rendimiento) el diseño de la velocidad es de suma importancia.

Con mucho, el lanzamiento más rápido es un lanzamiento estático que supera as es decir var message = (SpecificType)baseMessage superará var message = baseMessage as SpecificType. Este es un punto de interés solo porque un elenco estático no puede ayudarlo en su caso.

Como dos respuestas ya han mencionado realizar lo anterior de forma polimórfica utilizando un patrón de diseño podría ser la mejor solución, ya que solo agrega una llamada a un método virtual. Extraer el método común a una clase abstracta (o la firma de método común a la interfaz) es, de lejos, la solución más elegante. Hay una sobrecarga de llamar a un método virtual, pero esto se puede mitigar marcando los métodos específicos en los tipos derivados usando la palabra clave sealed.

Por último, utilice los genéricos siempre que sea posible para eliminar los moldes, ya que los métodos genéricos son una optimización en tiempo de compilación en comparación con la conversión en tiempo de ejecución.

Saludos,

Cuestiones relacionadas