2010-08-31 13 views
49

He notado que estas dos interfaces, y varias clases asociadas, se han agregado en .NET 4. Me parecen un poco superfluas; He leído varios blogs sobre ellos, pero todavía no puedo descifrar qué problema resuelven que fue complicado antes de .NET 4.¿Qué problema resuelve IStructuralEquatable y IStructuralComparable?

¿De qué sirve el IStructuralEquatable y el IStructuralComparable?

Respuesta

41

Todos los tipos de .NET admiten el método Object.Equals() que, por defecto, compara dos tipos de igualdad referencia. Sin embargo, a veces, también es deseable poder comparar dos tipos para igualdad estructural.

El mejor ejemplo de esto son las matrices, que con .NET 4 ahora implementan la interfaz IStructuralEquatable. Esto hace posible distinguir si está comparando dos matrices para la igualdad de referencia, o para la "igualdad estructural", si tienen el mismo número de elementos con los mismos valores en cada posición. He aquí un ejemplo:

int[] array1 = new int[] { 1, 5, 9 }; 
int[] array2 = new int[] { 1, 5, 9 }; 

// using reference comparison... 
Console.WriteLine(array1.Equals(array2)); // outputs false 

// now using the System.Array implementation of IStructuralEquatable 
Console.WriteLine(StructuralComparisons.StructuralEqualityComparer.Equals(array1, array2)); // outputs true 

Otros tipos estructurales que ponen en práctica la igualdad/comparabilidad incluyen tuplas y los tipos anónimos - la que ambos se benefician claramente de la capacidad de realizar la comparación en función de su estructura y contenido.

Una pregunta que no pidió es:

¿Por qué tenemos IStructuralComparable y IStructuralEquatable cuando ya existen las interfaces IComparable y IEquatable?

La respuesta que ofrecería es que, en general, es deseable diferenciar entre comparaciones de referencia y comparaciones estructurales. Normalmente se espera que si implementa IEquatable<T>.Equals, también anulará Object.Equals para que sea coherente. En este caso, ¿cómo apoyarías tanto la referencia como la igualdad estructural?

+2

Por qué no puede simplemente especificar un 'sí mismo que IEqualityComparer' ¿Haz esto? ¿Qué agrega la interfaz 'IStructuralEquatable' a esto? – thecoop

+0

@thecoop: hay dos razones. Primero, no todos los tipos implementan una sobrecarga de 'Equals' que acepta un' IEqualityComparer' - Array es un ejemplo, IIRC. En segundo lugar, es agradable proporcionar un comparador de igualdad, pero ¿qué ocurre si desea expresar el hecho de que cierto método requiere dos objetos que puedan compararse estructuralmente? Ser capaz de especificar 'IStructuralEquatable' /' IStructuralComparable' en tales casos es realmente útil. También sería inconveniente pasar un 'TupleComparer' o' ArrayComparer' donde quiera aplicar este tipo de comparación. Los dos enfoques no son mutuamente excluyentes. – LBushkin

+0

¿Cómo se relacionan estos comparadores con cosas como Dictionary y otras colecciones? Sé que el diccionario parece manejar las estructuras con sensatez, aunque lentamente en .Net 2.0; ¿.Net 4.0 (o 3.x para el caso) permite que las matrices se almacenen convenientemente en Diccionario (usando los contenidos de la matriz como la clave)? – supercat

11

Tenía la misma pregunta. Cuando ejecuté el ejemplo de LBushkin, me sorprendí al ver que obtuve una respuesta diferente. A pesar de que esa respuesta tiene 8 votos ascendentes, está mal. Después de un montón de 'reflector', aquí está mi opinión sobre las cosas.

Ciertos contenedores (matrices, tuplas, tipos anónimos) admiten IStructuralComparable e IStructuralEquatable.

IStructuralComparable admite una clasificación profunda y predeterminada.
IStructuralEquatable admite hash profundo y predeterminado.

{Tenga en cuenta que EqualityComparer<T> apoya poco profundo (nivel de sólo el 1 contenedor), hash por defecto.}

Por lo que yo veo esto sólo se expone a través de la clase StructuralComparisons. La única manera que puedo imaginar para hacer de este útil es hacer una clase StructuralEqualityComparer<T> ayudante de la siguiente manera:

public class StructuralEqualityComparer<T> : IEqualityComparer<T> 
    { 
     public bool Equals(T x, T y) 
     { 
      return StructuralComparisons.StructuralEqualityComparer.Equals(x,y); 
     } 

     public int GetHashCode(T obj) 
     { 
      return StructuralComparisons.StructuralEqualityComparer.GetHashCode(obj); 
     } 

     private static StructuralEqualityComparer<T> defaultComparer; 
     public static StructuralEqualityComparer<T> Default 
     { 
      get 
      { 
       StructuralEqualityComparer<T> comparer = defaultComparer; 
       if (comparer == null) 
       { 
        comparer = new StructuralEqualityComparer<T>(); 
        defaultComparer = comparer; 
       } 
       return comparer; 
      } 
     } 
    } 

Ahora podemos hacer un HashSet con los objetos que tienen contenedores dentro de los contenedores dentro de los contenedores.

 var item1 = Tuple.Create(1, new int[][] { new int[] { 1, 2 }, new int[] { 3 } }); 
     var item1Clone = Tuple.Create(1, new int[][] { new int[] { 1, 2 }, new int[] { 3 } }); 
     var item2 = Tuple.Create(1, new int[][] { new int[] { 1, 3 }, new int[] { 3 } }); 

     var set = new HashSet<Tuple<int, int[][]>>(StructuralEqualityComparer<Tuple<int, int[][]>>.Default); 
     Console.WriteLine(set.Add(item1));  //true 
     Console.WriteLine(set.Add(item1Clone)); //false 
     Console.WriteLine(set.Add(item2));  //true 

También podemos hacer que nuestro propio contenedor funcione bien con estos otros contenedores mediante la implementación de estas interfaces.

public class StructuralLinkedList<T> : LinkedList<T>, IStructuralEquatable 
    { 
     public bool Equals(object other, IEqualityComparer comparer) 
     { 
      if (other == null) 
       return false; 

      StructuralLinkedList<T> otherList = other as StructuralLinkedList<T>; 
      if (otherList == null) 
       return false; 

      using(var thisItem = this.GetEnumerator()) 
      using (var otherItem = otherList.GetEnumerator()) 
      { 
       while (true) 
       { 
        bool thisDone = !thisItem.MoveNext(); 
        bool otherDone = !otherItem.MoveNext(); 

        if (thisDone && otherDone) 
         break; 

        if (thisDone || otherDone) 
         return false; 

        if (!comparer.Equals(thisItem.Current, otherItem.Current)) 
         return false; 
       } 
      } 

      return true; 
     } 

     public int GetHashCode(IEqualityComparer comparer) 
     { 
      var result = 0; 
      foreach (var item in this) 
       result = result * 31 + comparer.GetHashCode(item); 

      return result; 
     } 

     public void Add(T item) 
     { 
      this.AddLast(item); 
     } 
    } 

Ahora podemos hacer un HashSet con elementos que tienen contenedores dentro de contenedores personalizados dentro de contenedores.

 var item1 = Tuple.Create(1, new StructuralLinkedList<int[]> { new int[] { 1, 2 }, new int[] { 3 } }); 
     var item1Clone = Tuple.Create(1, new StructuralLinkedList<int[]> { new int[] { 1, 2 }, new int[] { 3 } }); 
     var item2 = Tuple.Create(1, new StructuralLinkedList<int[]> { new int[] { 1, 3 }, new int[] { 3 } }); 

     var set = new HashSet<Tuple<int, StructuralLinkedList<int[]>>>(StructuralEqualityComparer<Tuple<int, StructuralLinkedList<int[]>>>.Default); 
     Console.WriteLine(set.Add(item1));  //true 
     Console.WriteLine(set.Add(item1Clone)); //false 
     Console.WriteLine(set.Add(item2));  //true 
3

Aquí es otro ejemplo que ilustra un posible uso de las dos interfaces:

var a1 = new[] { 1, 33, 376, 4}; 
var a2 = new[] { 1, 33, 376, 4 }; 
var a3 = new[] { 2, 366, 12, 12}; 

Debug.WriteLine(a1.Equals(a2)); // False 
Debug.WriteLine(StructuralComparisons.StructuralEqualityComparer.Equals(a1, a2)); // True 

Debug.WriteLine(StructuralComparisons.StructuralComparer.Compare(a1, a2)); // 0 
Debug.WriteLine(StructuralComparisons.StructuralComparer.Compare(a1, a3)); // -1 
+1

Por cierto, sería una buena idea agregar una restricción de tipo genérico a su StructuralEqualityComparer. p.ej. donde T: IStructuralEquatable – AndrewS

0

En la descripción de la IStructuralEquatable Interface Microsoft dice claramente (en la sección "Comentarios"):

La interfaz IStructuralEquatable le permite implementar comparaciones personalizadas para verificar la igualdad estructural de los objetos de recopilación .

Esto también se pone de manifiesto por el hecho de que esta interfaz reside en el espacio de nombres System.Collections.

0

F # empezó a utilizarlos porque .NET 4. (.net 2 is here)

Estas interfaces son cruciales para F #

let list1 = [1;5;9] 
let list2 = List.append [1;5] [9] 

printfn "are they equal? %b" (list1 = list2) 

list1.GetType().GetInterfaces().Dump() 

enter image description here

Cuestiones relacionadas