2011-06-01 18 views
28

Hoy me encontré con un error interesante que escribí. Tengo un conjunto de propiedades que se pueden establecer a través de un setter general. Estas propiedades pueden ser tipos de valores o tipos de referencia.Comparación de tipos de valores en caja

public void SetValue(TEnum property, object value) 
{ 
    if (_properties[ property ] != value) 
    { 
     // Only come here when the new value is different. 
    } 
} 

Al escribir una prueba unitaria para este método, descubrí que la condición siempre es cierta para los tipos de valores. No tardé mucho en descubrir que esto se debe a boxing/unboxing. No me llevó mucho tiempo, ya sea para ajustar el código a la siguiente:

public void SetValue(TEnum property, object value) 
{ 
    if (!_properties[ property ].Equals(value)) 
    { 
     // Only come here when the new value is different. 
    } 
} 

Lo que pasa es que no estoy del todo satisfecho con esta solución. Me gustaría mantener una comparación de referencia simple, a menos que el valor esté enmarcado.

La solución actual en la que estoy pensando solo llama al Equals() para los valores en caja. Hacer a check for a boxed values parece un poco exagerado. ¿No hay una manera más fácil?

+0

Sin duda, si usted quiere diverso comportamiento para los valores encuadrados, ¿entonces vas a necesitar verificar si estás tratando con un valor encuadrado? – LukeH

+0

Realiza una sobrecarga genérica de este método con el tipo T donde T: struct –

+1

@lukas, no funcionará a menos que exista una diferencia mayor que simplemente la 'T' y una restricción. –

Respuesta

15

Si necesita un comportamiento diferente cuando se está tratando con un tipo de valor, entonces obviamente vas a tener que realizar algún tipo de prueba. No necesita una verificación explícita para boxed value-types, ya que todos los tipos de valores se guardarán en cuadro ** debido a que el parámetro se escribe como object.

Este código debe cumplir con los criterios establecidos: Si es un value (en caja) Valor de tipo continuación, llamar al método polimórfico Equals, de lo contrario usar == para comprobar la igualdad de referencia.

public void SetValue(TEnum property, object value) 
{ 
    bool equal = ((value != null) && value.GetType().IsValueType) 
        ? value.Equals(_properties[property]) 
        : (value == _properties[property]); 

    if (!equal) 
    { 
     // Only come here when the new value is different. 
    } 
} 

(** Y, sí, sé que Nullable<T> es un tipo de valor con sus propias reglas especiales relativas al boxeo y unboxing, pero eso es prácticamente irrelevante aquí.)

+0

Gracias, esto parece funcionar perfectamente y no tiene una gran carga. No veo un aumento en el tiempo de ejecución promedio. –

+0

La sobrecarga está en el boxeo también llamando a getType() tiene un costo. Lo mejor es evitarlo generando el getter y el setter delegate sobre la marcha. Mantenga las cosas al tipo real nunca encajonadas. –

1

Dado que el tipo del parámetro de entrada es object, siempre obtendrá un valor enmarcado dentro del contexto del método.

Creo que su única posibilidad es cambiar la firma del método y escribir diferentes sobrecargas.

+0

Gracias, daré una oportunidad mañana. –

1

¿Qué tal esto:

if(object.ReferenceEquals(first, second)) { return; } 
if(first.Equals(second)) { return; } 

// they must differ, right? 

actualización

me di cuenta de esto no funciona como se esperaba para un determinado caso:

  • Para los tipos de valor, por lo que ReferenceEquals vuelve falsa volvemos al Equals, que se comporta como se esperaba.
  • Para tipos de referencia donde ReferenceEquals devuelve verdadero, los consideramos "iguales" como se esperaba.
  • Para los tipos de referencia donde ReferenceEquals devuelve falso y Equals devuelve falso, los consideramos "diferentes" como se esperaba.
  • Para los tipos de referencia, donde ReferenceEquals vuelve falsas y verdaderas Equals rendimientos, que los consideran "igual" a pesar de que queremos "diferente"

Así que la lección es "no reciben inteligente"

+0

si el valor fue simplemente enmarcado, como en el caso de los tipos de valor, el primer 'if' siempre dará como resultado falso, por lo tanto, no resolverá el problema de OP. – Simone

+1

El problema, tal como lo entiendo, es "me gustaría mantener una comparación de referencia simple, a menos que el valor esté enmarcado". Esto haría eso. Pero como dice la respuesta de Sean, "si alguien ha anulado .Equals() en una clase es porque quieren cambiar la semántica de igualdad para ese tipo, y es mejor dejar que eso suceda si no tienes una razón convincente para no hacerlo" . Supuse que el asker tiene una razón convincente. –

+0

Esa es una suposición correcta. ; p –

10

Iguales() es generalmente el enfoque preferido.

La implementación predeterminada de .Equals() hace una comparación de referencia simple para los tipos de referencia, por lo que en la mayoría de los casos eso es lo que obtendrás. Equals() podría haber sido reemplazado para proporcionar algún otro comportamiento, pero si alguien ha anulado .Equals() en una clase es porque quiere cambiar la semántica de igualdad para ese tipo, y es mejor dejar que eso suceda si no lo hace tener una razón convincente para no hacerlo. Superarlo usando == puede generar confusión cuando su clase ve dos cosas diferentes cuando cada clase acepta que son iguales.

+0

El problema es exactamente que 'Igual' podría haber sido anulado. Aunque dos objetos son iguales, eso no significa que no se haya establecido un objeto nuevo (con una referencia diferente). –

0

supongo

me gustaría mantener una simple comparación de referencia, a menos que se encajona el valor.

es algo equivalente a

Si se encajona el valor, voy a hacer una "comparación simple referencia" no.

Esto significa que lo primero que tendrá que hacer es comprobar si el valor está encasillado o no.

Si existe un método para comprobar si un objeto es un tipo de valor encuadrado o no, debe ser al menos tan complejo como el método de "exageración" al que proporcionó el enlace a menos que no sea la manera más simple. No obstante, debe haber una "forma más simple" de determinar si un objeto es un tipo de valor encuadrado o no. Es poco probable que esta "forma más simple" sea más simple que simplemente usar el método Equals() del objeto, pero he marcado esta pregunta para averiguarlo por si acaso.

(no estoy seguro si era lógico)

Cuestiones relacionadas