Qual problema IStructuralEquatable e IStructuralComparable resolver?
-
25-09-2019 - |
Pergunta
Eu notei essas duas interfaces, e várias classes associadas, foram adicionadas .NET 4.Eles parecem um pouco supérfluo para mim;Eu li vários blogs sobre eles, mas eu ainda não consigo descobrir qual é o problema que eles resolvem que era complicado antes .NET 4.
O que estás a usar IStructuralEquatable
e IStructuralComparable
?
Solução
Todos os tipos no .NET suportam o Object.Equals()
método que, por padrão, compara dois tipos para igualdade de referência. No entanto, às vezes, também é desejável poder comparar dois tipos para igualdade estrutural.
O melhor exemplo disso são as matrizes, que com .NET 4 agora implementa o IStructuralEquatable
interface. Isso torna possível distinguir se você está comparando duas matrizes para igualdade de referência ou para "igualdade estrutural" - se elas têm o mesmo número de itens com os mesmos valores em cada posição. Aqui está um exemplo:
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
Outros tipos que implementam a igualdade/comparabilidade estrutural incluem tuplas e tipos anônimos - que se beneficiam claramente da capacidade de realizar a comparação com base em sua estrutura e conteúdo.
Uma pergunta que você não fez é:
Por que temos
IStructuralComparable
eIStructuralEquatable
Quando já existe oIComparable
eIEquatable
interfaces?
A resposta que eu oferecia é que, em geral, é desejável diferenciar comparações de referência e comparações estruturais. Normalmente espera que, se você implementar IEquatable<T>.Equals
você também vai substituir Object.Equals
para ser consistente. Nesse caso, como você apoiaria a igualdade de referência e estrutural?
Outras dicas
Eu tive a mesma pergunta. Quando corri o exemplo de Lbushkin, fiquei surpreso ao ver que recebi uma resposta diferente! Embora essa resposta tenha 8 votos, está errado. Depois de muito 'refletor', aqui está minha opinião sobre as coisas.
Certos recipientes (matrizes, tuplas, tipos anônimos) suportam comparação e istructurlequatable.
O IStructuralComparable suporta uma classificação profunda e padrão.
O istructuralequatable suporta hash profunda e padrão.
{Observe que EqualityComparer<T>
suporta rasos (apenas 1 nível de contêiner), hash de padrão.}
Tanto quanto eu vejo, isso só é exposto através da classe StructuralComparisons. A única maneira de descobrir que isso é útil é fazer um StructuralEqualityComparer<T>
Classe Helper da seguinte maneira:
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;
}
}
}
Agora, podemos fazer um hashset com itens com contêineres dentro de contêineres dentro dos contêineres.
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
Também podemos fazer nosso próprio contêiner jogar bem com esses outros contêineres implementando essas 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);
}
}
Agora, podemos fazer um hashset com itens com contêineres dentro de contêineres personalizados dentro de contêineres.
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
Aqui está outro exemplo que ilustra um possível uso das duas 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
A descrição do IStructuralEquatable
Interface diz (na seção "Observações"):
o
IStructuralEquatable
A interface permite implementar comparações personalizadas para verificar a igualdade estrutural de Objetos de coleção.
Isso também fica claro pelo fato de que essa interface reside no System.Collections
espaço para nome.
F# começou a usá -los desde .NET 4. ( .NET 2 está aqui)
Essas interfaces são cruciais 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()
C# em poucas palavras livro:
Porque a Matriz é uma classe, as matrizes são sempre (si)
reference types
, independentemente da matriz do tipo de elemento.Isso significa que a instruçãoarrayB = arrayA
resultados em duas variáveis que referenciam a mesma matriz.Da mesma forma, duas distintas matrizes irão sempre falhar um teste de igualdade—a menos que você use um costume igualdade comparador.Quadro 4.0 introduziu a finalidade de comparar os elementos em matrizes que você pode o acesso através doStructuralComparisons
escreva.
object[] a1 = { "string", 123, true};
object[] a2 = { "string", 123, true};
Console.WriteLine(a1 == a2); // False
Console.WriteLine(a1.Equals(a2)); // False
IStructuralEquatable se1 = a1;
Console.WriteLine(se1.Equals(a2, StructuralComparisons.StructuralEqualityComparer)); // True
Console.WriteLine(StructuralComparisons.StructuralEqualityComparer.Equals(a1, a2)); // True
object[] a3 = {"string", 123, true};
object[] a4 = {"string", 123, true};
object[] a5 = {"string", 124, true};
IStructuralComparable se2 = a3;
Console.WriteLine(se2.CompareTo(a4, StructuralComparisons.StructuralComparer)); // 0
Console.WriteLine(StructuralComparisons.StructuralComparer.Compare(a3, a4)); // 0
Console.WriteLine(StructuralComparisons.StructuralComparer.Compare(a4, a5)); // -1
Console.WriteLine(StructuralComparisons.StructuralComparer.Compare(a5, a4)); // 1