2012-01-12 12 views
14

Viendo de Artech's blog y luego tuvimos una discusión en los comentarios. Dado que ese blog está escrito solo en chino, estoy tomando una breve explicación aquí. El código para reproducir:GetHashCode e Iguales se implementan incorrectamente en System.Attribute?

[AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = true)] 
public abstract class BaseAttribute : Attribute 
{ 
    public string Name { get; set; } 
} 

public class FooAttribute : BaseAttribute { } 

[Foo(Name = "A")] 
[Foo(Name = "B")] 
[Foo(Name = "C")] 
public class Bar { } 

//Main method 
var attributes = typeof(Bar).GetCustomAttributes(true).OfType<FooAttribute>().ToList<FooAttribute>(); 
var getC = attributes.First(item => item.Name == "C"); 
attributes.Remove(getC); 
attributes.ForEach(a => Console.WriteLine(a.Name)); 

El código recibe toda FooAttribute y elimina la persona cuyo nombre es "C". Obviamente, la salida es "A" y "B"? Si todo marchara bien, no verías esta pregunta. De hecho, obtendrás "AC" "BC" o incluso corregirás "AB" teóricamente (obtuve AC en mi máquina, y el autor del blog obtuvo BC). El problema es el resultado de la implementación de GetHashCode/Equals en System.Attribute. Un fragmento de la aplicación:

[SecuritySafeCritical] 
    public override int GetHashCode() 
    { 
     Type type = base.GetType(); 
 //*****NOTICE***** 
     FieldInfo[] fields = type.GetFields(BindingFlags.NonPublic 
      | BindingFlags.Public 
      | BindingFlags.Instance); 
 object obj2 = null; 
     for (int i = 0; i < fields.Length; i++) 
     { 
      object obj3 = ((RtFieldInfo) fields[i]).InternalGetValue(this, false, false); 
      if ((obj3 != null) && !obj3.GetType().IsArray) 
      { 
       obj2 = obj3; 
      } 
      if (obj2 != null) 
      { 
       break; 
      } 
     } 
     if (obj2 != null) 
     { 
      return obj2.GetHashCode(); 
     } 
     return type.GetHashCode(); 
    } 

Utiliza Type.GetFields lo que las propiedades heredadas de clase base son ignorados, por lo tanto, la equivalencia de los tres casos de FooAttribute (y entonces la Remove método toma uno aleatoriamente). Entonces la pregunta es: ¿hay alguna razón especial para la implementación? ¿O solo es un error?

+0

Yo diría que es un error, aunque tengo dificultades para imaginar un escenario del mundo real donde podría causar un problema. Podría considerar informarlo en connect.microsoft.com. – Joe

+0

El error está en Equals(), está bien que GetHashCode() devuelva valores idénticos. De acuerdo, publica esto en connect. Realmente dudo que lo arreglen porque sería un cambio radical. –

+0

@HansPassant: Tiene razón. Aquí publico el código de 'GetHashCode' solo porque el autor publicó el código y no tengo desensamblador en esta pc. –

Respuesta

7

Un error claro, no. Una buena idea, tal vez o quizás no.

¿Qué significa que una cosa sea igual a otra? Podríamos ser bastante filosóficos, si realmente quisiéramos.

Siendo sólo un poco filosófica, hay algunas cosas que deben estar en posesión:

  1. La igualdad es reflexiva: Identidad implica la igualdad. x.Equals(x) debe contener.
  2. La igualdad es simétrica. Si x.Equals(y) entonces y.Equals(x) y si !x.Equals(y) entonces !y.Equals(x).
  3. La igualdad es transitiva. Si x.Equals(y) y y.Equals(z), entonces x.Equals(z).

Hay algunos otros, aunque solo estos pueden ser reflejados directamente por el código para Equals() solo.

Si una implementación de una anulación de los object.Equals(object), IEquatable<T>.Equals(T), IEqualityComparer.Equals(object, object), IEqualityComparer<T>.Equals(T, T), == o de != no cumple con lo anterior, es un error claro. El otro método que refleja igualdad en .NET es object.GetHashCode(), IEqualityComparer.GetHashCode(object) y IEqualityComparer<T>.GetHashCode(T). Aquí está la regla simple:

Si a.Equals(b) entonces debe contener que a.GetHashCode() == b.GetHashCode(). El equivalente se mantiene para IEqualityComparer y IEqualityComparer<T>.

Si eso no se cumple, de nuevo tenemos un error.

Más allá de eso, no hay reglas generales sobre lo que la igualdad debe significar.Depende de la semántica de la clase proporcionada por sus propias anulaciones Equals() o por las que le imponga un comparador de igualdad. Por supuesto, esa semántica debería ser descaradamente obvia o estar documentada en la clase o en el comparador de igualdad.

En todo, ¿cómo funciona una Equals y/o una GetHashCode tienen un error:

  1. Si falla para proporcionar las propiedades reflexiva, simétrica y transitiva detallados anteriormente.
  2. Si la relación entre GetHashCode y Equals no es la anterior.
  3. Si no coincide con su semántica documentada.
  4. Si arroja una excepción inapropiada.
  5. Si se desvía en un bucle infinito.
  6. En la práctica, si toma tanto tiempo volver como para paralizar las cosas, aunque uno podría argumentar que aquí hay una teoría vs. práctica.

Con las sustituciones en Attribute, los iguales tiene las propiedades reflexiva, simétrica y transitiva, es GetHashCode no coincidir con ella, y la documentación es Equals la redefinición es:

Esta API es compatible con el. NET Framework y no está destinado a ser utilizado directamente desde su código.

¡Realmente no se puede decir que su ejemplo lo desmienta!

Como el código del que se queja no falla en ninguno de estos puntos, no es un error.

Hay un insecto, aunque en este código:

var attributes = typeof(Bar).GetCustomAttributes(true).OfType<FooAttribute>().ToList<FooAttribute>(); 
var getC = attributes.First(item => item.Name == "C"); 
attributes.Remove(getC); 

En primer lugar, solicita un elemento que cumple un criterio, y luego pedir uno que es igual al que se elimine. No hay motivo sin examinar la semántica de igualdad para el tipo en cuestión para esperar que se elimine getC.

Lo que debe hacer es:

bool calledAlready; 
attributes.RemoveAll(item => { 
    if(!calledAlready && item.Name == "C") 
    { 
    return calledAlready = true; 
    } 
}); 

Es decir, se utiliza un predicado que coincide con el primer atributo con Name == "C" y ningún otro.

+0

La expectativa de que '==' tenga alguna relación con 'Iguales' es IMHO inútil, dado el grado en que el comportamiento de los tipos incorporados con' == 'ni siquiera representa una relación de equivalencia (los tipos de punto flotante pueden ni siquiera manejamos la reflexividad, y la forma en que las transmisiones implícitas funcionan de la única manera de hacer que las comparaciones entre 'largo' y' doble' sean transitivas sería definir '(largo, doble)' y '(doble, largo)' sobrecargas de ' == '. Quizás el Framework debería haber hecho que' == 'sea una relación de equivalencia en todos los casos donde se compila, pero como no lo hace, creo que es mejor ... – supercat

+0

... ver' == 'y 'Igual' son conceptos totalmente independientes que pueden tener cierta superposición, pero no tienen ninguna relación real. – supercat

0

Sí, un error como otros ya han mencionado en los comentarios. Puedo sugerir algunas soluciones posibles:

Opción 1, No use inheritence en la clase Attribute, esto permitirá que la implementación predeterminada funcione. La otra opción es usar un comparador personalizado para asegurarse de que está utilizando igualdad de referencia al eliminar el artículo. Puede implementar un comparador con la suficiente facilidad. Simplemente use Object.ReferenceEquals para comparar y para su uso puede usar el código hash del tipo o usar System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode.

public sealed class ReferenceEqualityComparer<T> : IEqualityComparer<T> 
{ 
    bool IEqualityComparer<T>.Equals(T x, T y) 
    { 
     return Object.ReferenceEquals(x, y); 
    } 
    int IEqualityComparer<T>.GetHashCode(T obj) 
    { 
     return System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(obj); 
    } 
} 
+0

¿De qué manera no cumple con los requisitos generales para' Equals' y 'GetHashCode' o no puede coincidir con el documentación para 'Attribute.Equals'? –

+0

Una búsqueda más caso ionable es el hecho de que 'RuntimeHelpers.GetHashCode' no funcionará como lo desea si lo ejecuta en Windows Phone 7. Aquí el" no está destinado a ser utilizado directamente ... "existe en la respuesta de MS al informe de error, y no en los documentos reales. Recomiendo emitir IL como lo hago en https://github.com/hackcraft/Ariadne/blob/master/Collections/ReferenceEqualityComparer.cs en lugar de usarlo. –

+0

@Jon Hanna, un error ya que la intención era una comparación de valor de campo y no funciona correctamente. En cuanto a WP7, tiene razón en que la API no es compatible; sin embargo, el OP no hace mención de un objetivo WP7. –

Cuestiones relacionadas