Quel est le problème résolu par IStructuralEquatable et IStructuralComparable?

J’ai remarqué que ces deux interfaces et plusieurs classes associées ont été ajoutées dans .NET 4. Elles me semblent un peu superflues; J’ai lu plusieurs blogs à leur sujet, mais je n’arrive toujours pas à comprendre quel problème ils ont résolu avant .NET 4.

A quoi sert IStructuralEquatable et IStructuralComparable ?

    Tous les types de .NET prennent en charge la méthode Object.Equals() qui, par défaut, compare deux types d’ égalité de référence . Cependant, parfois, il est également souhaitable de pouvoir comparer deux types d’ égalité structurelle .

    Le meilleur exemple de ceci est les tableaux, qui avec .NET 4 implémentent maintenant l’interface IStructuralEquatable . Cela permet de distinguer si vous comparez deux tableaux pour une égalité de référence ou pour une “égalité structurelle” – s’ils ont le même nombre d’éléments avec les mêmes valeurs dans chaque position. Voici un exemple:

     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 

    Parmi les autres types qui mettent en œuvre l’égalité structurelle / la comparabilité, on trouve les tuples et les types anonymes – qui bénéficient tous deux clairement de la possibilité d’effectuer des comparaisons en fonction de leur structure et de leur contenu.

    Une question que vous n’avez pas posée est la suivante:

    Pourquoi avons-nous IStructuralComparable et IStructuralEquatable quand il existe déjà les interfaces IComparable et IEquatable ?

    La réponse que je proposerais est que, en général, il est souhaitable de différencier les comparaisons de références et les comparaisons structurelles. Il est normalement prévu que si vous implémentez IEquatable.Equals vous remplacerez également Object.Equals pour être cohérent. Dans ce cas, comment soutiendriez-vous l’égalité de référence et l’égalité structurelle?

    J’ai eu la même question. Quand j’ai couru l’exemple de LBushkin, j’ai été surpris de voir que j’avais une réponse différente! Même si cette réponse comporte 8 votes positifs, c’est faux. Après beaucoup de reflections, voici ma vision des choses.

    Certains conteneurs (tableaux, tuples, types anonymes) prennent en charge IStructuralComparable et IStructuralEquatable.

    IStructuralComparable prend en charge le sorting profond par défaut.
    IStructuralEquatable prend en charge le hachage profond par défaut.

    {Notez que EqualityComparer prend en charge le hachage par défaut (1 seul niveau de conteneur).

    Pour autant que je voie cela est seulement exposé à travers la classe StructuralComparisons. La seule façon que je puisse trouver utile pour que cela soit utile est de créer une classe d’assistance StructuralEqualityComparer comme suit:

      public class StructuralEqualityComparer : IEqualityComparer { 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 defaultComparer; public static StructuralEqualityComparer Default { get { StructuralEqualityComparer comparer = defaultComparer; if (comparer == null) { comparer = new StructuralEqualityComparer(); defaultComparer = comparer; } return comparer; } } } 

    Maintenant, nous pouvons créer un HashSet avec des objects contenant des conteneurs dans des conteneurs à l’intérieur de conteneurs.

      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>(StructuralEqualityComparer>.Default); Console.WriteLine(set.Add(item1)); //true Console.WriteLine(set.Add(item1Clone)); //false Console.WriteLine(set.Add(item2)); //true 

    Nous pouvons également faire en sorte que notre propre conteneur joue bien avec ces autres conteneurs en implémentant ces interfaces.

     public class StructuralLinkedList : LinkedList, IStructuralEquatable { public bool Equals(object other, IEqualityComparer comparer) { if (other == null) return false; StructuralLinkedList otherList = other as StructuralLinkedList; 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); } } 

    Maintenant, nous pouvons créer un HashSet avec des éléments ayant des conteneurs dans des conteneurs personnalisés à l’intérieur des conteneurs.

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

    Voici un autre exemple qui illustre une utilisation possible des deux 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 

    Dans la description de l’ interface IStructuralEquatable Microsoft dit clairement (dans la section “Remarques”):

    L’interface IStructuralEquatable vous permet d’implémenter des comparaisons personnalisées pour vérifier l’égalité structurelle des objects de collection .

    Cela est également clair par le fait que cette interface réside dans l’espace de noms System.Collections .

    F # a commencé à les utiliser depuis .net 4. ( .net 2 est ici )

    Ces interfaces sont cruciales pour F #

     let list1 = [1;5;9] let list2 = List.append [1;5] [9] printfn "are they equal? %b" (list1 = list2) list1.GetType().GetInterfaces().Dump() 

    entrer la description de l'image ici