.net - ¿Qué problema resuelve IStructuralEquatable y IStructuralComparable?
equality icomparable (5)
Me he dado cuenta de 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 solucionaron antes .NET 4.
¿Para qué sirven IStructuralEquatable
y IStructuralComparable
?
Aquí hay 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
En la descripción de la Interfaz IStructuralEquatable
Microsoft dice claramente (en la sección "Observaciones"):
La interfaz
IStructuralEquatable
permite implementar comparaciones personalizadas para verificar la igualdad estructural de los objetos de colección .
Esto también queda claro por el hecho de que esta interfaz reside en el espacio de nombres System.Collections
.
F # comenzó a usarlos desde .net 4. ( .net 2 está aquí )
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()
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 clasificación profunda y predeterminada.
IStructuralEquatable admite hashing profundo y predeterminado.
{Tenga en cuenta que EqualityComparer<T>
admite poco profundo (solo 1 nivel de contenedor), hashing predeterminado.}
Por lo que veo, esto solo está expuesto a través de la clase StructuralComparisons. La única forma en que puedo averiguar para hacer esto útil es hacer una clase de ayuda StructuralEqualityComparer<T>
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 elementos que tienen contenedores dentro de contenedores dentro de 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
Todos los tipos en .NET admiten el método Object.Equals()
que, de forma predeterminada, compara dos tipos para la igualdad de referencia . Sin embargo, a veces, también es deseable poder comparar dos tipos para la 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. Aquí hay 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 que implementan la igualdad / comparabilidad estructural incluyen tuplas y tipos anónimos, que se benefician claramente de la capacidad de realizar una comparación basada en su estructura y contenido.
Una pregunta que no hizo es:
¿Por qué tenemos
IStructuralComparable
eIStructuralEquatable
cuando ya existen las interfacesIComparable
eIEquatable
?
La respuesta que ofrecería es que, en general, es deseable diferenciar entre las comparaciones de referencia y las comparaciones estructurales. Normalmente se espera que si implementa IEquatable<T>.Equals
también anule Object.Equals
para que sea coherente. En este caso, ¿cómo apoyarías tanto la referencia como la igualdad estructural?