62 votes

Ce problème ne IStructuralEquatable et IStructuralComparable résoudre?

J'ai remarqué ces deux interfaces, et plusieurs classes associées, ont été ajoutés .NET 4. Ils semblent un peu superflu pour moi; je l'ai lu plusieurs blogs à ce sujet, mais je ne peux toujours pas comprendre ce problème à résoudre qui a été difficile avant .NET 4.

À quoi servent - IStructuralEquatable et IStructuralComparable?

54voto

LBushkin Points 60611

Tous les types .NET soutien à l' Object.Equals() méthode qui, par défaut, compare les deux types de référence de l'égalité. Cependant, parfois, il est également souhaitable de pouvoir comparer les deux types pour l'égalité structurelle.

Le meilleur exemple de cela est des tableaux, qui avec .NET 4 mettre en œuvre l' IStructuralEquatable interface. Cela permet de distinguer si vous avez de la comparaison de deux tableaux de référence de l'égalité, ou pour "l'é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

D'autres types qui mettent en œuvre l'égalité structurelle/comparabilité inclure les tuples et les types anonymes - qui à la fois clairement bénéficier de la possibilité d'effectuer la comparaison est basée sur leur structure et leur contenu.

Une question à ne pas poser est:

Pourquoi avons-nous IStructuralComparable et IStructuralEquatable quand il y en a déjà l' IComparable et IEquatable interfaces?

La réponse, je voudrais proposer est que, en général, il est souhaitable de différencier de référence des comparaisons et des comparaisons structurelles. Il est normalement prévu que, si vous implémentez IEquatable<T>.Equals vous permettra également de remplacer Object.Equals pour être cohérent. Dans ce cas, comment seriez-vous favorable à la fois de référence et de l'égalité structurelle?

21voto

jyoung Points 2598

J'ai eu la même question. Quand j'ai couru LBushkin l'exemple, j'ai été surpris de voir que j'ai eu une réponse différente! Même si cette réponse a 8 upvotes, c'est mal. Après beaucoup de " réflecteur avec, ici, c'est ma façon de voir les choses.

Certains conteneurs (les tableaux, les tuples, les types anonymes) soutien IStructuralComparable et IStructuralEquatable.

IStructuralComparable prend en charge profonde, tri par défaut.
IStructuralEquatable prend en charge profonde, par défaut de hachage.

{Note qu' EqualityComparer<T> prend en charge superficielle (1 seul niveau du conteneur), par défaut de hachage.}

Aussi loin que je vois c'est seulement exposée à travers la StructuralComparisons classe. La seule façon que je peux comprendre pour faire de cette utile est de faire de l' StructuralEqualityComparer<T> de la classe helper comme suit:

    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;
            }
        }
    }

Maintenant, nous pouvons faire un HashSet avec des articles ayant des conteneurs à l'intérieur de conteneurs à l'intérieur des 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<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

Nous pouvons également apporter notre propre conteneur de bien jouer avec ces autres conteneurs par la mise en œuvre de ces 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);
        }
    }

Maintenant, nous pouvons faire un HashSet avec des articles ayant des conteneurs à l'intérieur des conteneurs personnalisés dans les conteneurs.

        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

5voto

Marc Sigrist Points 663

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X