Question

Arrière-plan

J'ai deux objets qui ont une association bidirectionnelle entre eux dans un projet C # Je travaille sur. Je dois être en mesure de vérifier l'égalité de valeur (par rapport à l'égalité de référence) pour un certain nombre de raisons (par exemple pour les utiliser dans des collections) et donc je suis IEquatable et les mise en œuvre de fonctions connexes.

Hypothèses

  • J'utilise C # 3.0, .NET 3.5 et Visual Studio 2008 (bien qu'il ne devrait pas d'importance pour la comparaison de l'égalité question de routine).

Contraintes

Toute solution doit:

  • Permettre une association bidirectionnelle à rester intacte tout en permettant la vérification de l'égalité de valeur.
  • Permettre aux utilisations externes de la classe d'appeler equals (Object obj) ou Equals (T classe) de IEquatable et reçoivent le bon comportement (comme dans System.Collections.Generic).

Problème

Lors de la mise en œuvre IEquatable pour fournir la vérification de la valeur d'égalité sur les types avec association bidirectionnelle, récursion infinie se produit entraînant un débordement de pile.

NOTE:. De même, en utilisant tous les champs d'une classe dans le calcul GetHashCode se traduira par une récursion infinie similaire et résultant problème de débordement de pile


Question

Comment vérifier l'égalité de valeur entre deux objets qui ont une association bidirectionnelle sans provoquer un débordement de pile?


code

NOTE: Ce code est théorique pour afficher la question, ne pas montrer la conception de classe réelle que je utilise qui est en cours d'exécution dans ce problème

using System;

namespace EqualityWithBiDirectionalAssociation
{

    public class Person : IEquatable<Person>
    {
        private string _firstName;
        private string _lastName;
        private Address _address;

        public Person(string firstName, string lastName, Address address)
        {
            FirstName = firstName;
            LastName = lastName;
            Address = address;
        }

        public virtual Address Address
        {
            get { return _address; }
            set { _address = value; }
        }

        public virtual string FirstName
        {
            get { return _firstName; }
            set { _firstName = value; }
        }

        public virtual string LastName
        {
            get { return _lastName; }
            set { _lastName = value; }
        }

        public override bool Equals(object obj)
        {
            // Use 'as' rather than a cast to get a null rather an exception
            // if the object isn't convertible
            Person person = obj as Person;
            return this.Equals(person);
        }

        public override int GetHashCode()
        {
            string composite = FirstName + LastName;
            return composite.GetHashCode();
        }


        #region IEquatable<Person> Members

        public virtual bool Equals(Person other)
        {
            // Per MSDN documentation, x.Equals(null) should return false
            if ((object)other == null)
            {
                return false;
            }

            return (this.Address.Equals(other.Address)
                && this.FirstName.Equals(other.FirstName)
                && this.LastName.Equals(other.LastName));
        }

        #endregion

    }

    public class Address : IEquatable<Address>
    {
        private string _streetName;
        private string _city;
        private string _state;
        private Person _resident;

        public Address(string city, string state, string streetName)
        {
            City = city;
            State = state;
            StreetName = streetName;
            _resident = null;
        }

        public virtual string City
        {
            get { return _city; }
            set { _city = value; }
        }

        public virtual Person Resident
        {
            get { return _resident; }
            set { _resident = value; }
        }

        public virtual string State
        {
            get { return _state; }
            set { _state = value; }
        }

        public virtual string StreetName
        {
            get { return _streetName; }
            set { _streetName = value; }
        }

        public override bool Equals(object obj)
        {
            // Use 'as' rather than a cast to get a null rather an exception
            // if the object isn't convertible
            Address address = obj as Address;
            return this.Equals(address);
        }

        public override int GetHashCode()
        {
            string composite = StreetName + City + State;
            return composite.GetHashCode();
        }


        #region IEquatable<Address> Members

        public virtual bool Equals(Address other)
        {
            // Per MSDN documentation, x.Equals(null) should return false
            if ((object)other == null)
            {
                return false;
            }

            return (this.City.Equals(other.City)
                && this.State.Equals(other.State)
                && this.StreetName.Equals(other.StreetName)
                && this.Resident.Equals(other.Resident));
        }

        #endregion
    }

    public class Program
    {
        static void Main(string[] args)
        {
            Address address1 = new Address("seattle", "washington", "Awesome St");
            Address address2 = new Address("seattle", "washington", "Awesome St");

            Person person1 = new Person("John", "Doe", address1);

            address1.Resident = person1;
            address2.Resident = person1;

            if (address1.Equals(address2)) // <-- Generates a stack overflow!
            {
                Console.WriteLine("The two addresses are equal");
            }

            Person person2 = new Person("John", "Doe", address2);
            address2.Resident = person2;

            if (address1.Equals(address2)) // <-- Generates a stack overflow!
            {
                Console.WriteLine("The two addresses are equal");
            }

            Console.Read();
        }
    }
}
Était-ce utile?

La solution 2

Si la restructuration de la classe pour supprimer l'association bidirectionnelle est possible et réduit le nombre de problèmes liés à la mise en œuvre, cette solution est préférée.

Si cette refonte est impossible ou introduit égales ou supérieures des problèmes de mise en œuvre, alors une solution possible est d'utiliser une méthode spécialisée Égal à appeler par Equals méthodes des classes impliquées dans l'association bidirectionnelle. Comme Mehrdad a déclaré, cela ne devrait pas être trop grand problème car les exigences demandent explicitement ce couplage, de sorte que vous ne sont pas l'introduction d'un en faisant cela.


code

Voici une mise en œuvre de ce qui maintient les méthodes spécialisées de contrôle que leurs propres champs. Cela réduit les problèmes d'entretien vs ayant chaque classe faire une comparaison par propriété de l'autre classe.

using System;

namespace EqualityWithBiDirectionalAssociation
{

    public class Person : IEquatable<Person>
    {
        private string _firstName;
        private string _lastName;
        private Address _address;

        public Person(string firstName, string lastName, Address address)
        {
            FirstName = firstName;
            LastName = lastName;
            Address = address;
        }

        public virtual Address Address
        {
            get { return _address; }
            set { _address = value; }
        }

        public virtual string FirstName
        {
            get { return _firstName; }
            set { _firstName = value; }
        }

        public virtual string LastName
        {
            get { return _lastName; }
            set { _lastName = value; }
        }

        public override bool Equals(object obj)
        {
            // Use 'as' rather than a cast to get a null rather an exception
            // if the object isn't convertible
            Person person = obj as Person;
            return this.Equals(person);
        }

        public override int GetHashCode()
        {
            string composite = FirstName + LastName;
            return composite.GetHashCode();
        }

        internal virtual bool EqualsIgnoringAddress(Person other)
        {
            // Per MSDN documentation, x.Equals(null) should return false
            if ((object)other == null)
            {
                return false;
            }

            return ( this.FirstName.Equals(other.FirstName)
                && this.LastName.Equals(other.LastName));
        }

        #region IEquatable<Person> Members

        public virtual bool Equals(Person other)
        {
            // Per MSDN documentation, x.Equals(null) should return false
            if ((object)other == null)
            {
                return false;
            }

            return (this.Address.EqualsIgnoringPerson(other.Address)   // Don't have Address check it's person
                && this.FirstName.Equals(other.FirstName)
                && this.LastName.Equals(other.LastName));
        }

        #endregion

    }

    public class Address : IEquatable<Address>
    {
        private string _streetName;
        private string _city;
        private string _state;
        private Person _resident;

        public Address(string city, string state, string streetName)
        {
            City = city;
            State = state;
            StreetName = streetName;
            _resident = null;
        }

        public virtual string City
        {
            get { return _city; }
            set { _city = value; }
        }

        public virtual Person Resident
        {
            get { return _resident; }
            set { _resident = value; }
        }

        public virtual string State
        {
            get { return _state; }
            set { _state = value; }
        }

        public virtual string StreetName
        {
            get { return _streetName; }
            set { _streetName = value; }
        }

        public override bool Equals(object obj)
        {
            // Use 'as' rather than a cast to get a null rather an exception
            // if the object isn't convertible
            Address address = obj as Address;
            return this.Equals(address);
        }

        public override int GetHashCode()
        {
            string composite = StreetName + City + State;
            return composite.GetHashCode();
        }



        internal virtual bool EqualsIgnoringPerson(Address other)
        {
            // Per MSDN documentation, x.Equals(null) should return false
            if ((object)other == null)
            {
                return false;
            }

            return (this.City.Equals(other.City)
                && this.State.Equals(other.State)
                && this.StreetName.Equals(other.StreetName));
        }

        #region IEquatable<Address> Members

        public virtual bool Equals(Address other)
        {
            // Per MSDN documentation, x.Equals(null) should return false
            if ((object)other == null)
            {
                return false;
            }

            return (this.City.Equals(other.City)
                && this.State.Equals(other.State)
                && this.StreetName.Equals(other.StreetName)
                && this.Resident.EqualsIgnoringAddress(other.Resident));
        }

        #endregion
    }

    public class Program
    {
        static void Main(string[] args)
        {
            Address address1 = new Address("seattle", "washington", "Awesome St");
            Address address2 = new Address("seattle", "washington", "Awesome St");

            Person person1 = new Person("John", "Doe", address1);

            address1.Resident = person1;
            address2.Resident = person1;

            if (address1.Equals(address2)) // <-- No stack overflow!
            {
                Console.WriteLine("The two addresses are equal");
            }

            Person person2 = new Person("John", "Doe", address2);
            address2.Resident = person2;

            if (address1.Equals(address2)) // <-- No a stack overflow!
            {
                Console.WriteLine("The two addresses are equal");
            }

            Console.Read();
        }
    }
}

Sortie

  

Les deux adresses sont égaux.

     

Les deux adresses sont égaux.

Autres conseils

Vous couplage des classes trop serré et le mélange des valeurs et des références. Vous devriez soit envisager de vérifier l'égalité de référence pour l'une des classes ou leur faire prendre conscience de l'autre (en fournissant une méthode de internal spécialisée Equals pour la classe spécifique ou vérifier manuellement l'égalité de la valeur de l'autre classe). Cela ne devrait pas être un gros problème car vos exigences demandent explicitement ce couplage de sorte que vous n'êtes pas un présentiez en faisant cela.

Je pense ici la meilleure solution est de briser la classe d'adresse en deux parties

  1. Informations sur l'adresse de base (dire d'adresse)
  2. 1 + personne information (dire OccupiedAddress)

Ensuite, il serait assez simple dans la classe de personne pour comparer les informations d'adresse de base sans créer de SO.

Oui, cela ne crée un peu de couplage dans votre code parce que personne va maintenant avoir un peu de connaissance intérieure sur la façon dont fonctionne OccupiedAddress. Mais ces classes ont déjà couplage serré si vraiment vous avez fait le problème pas pire.

La solution idéale serait de découpler complètement ces classes.

public override bool Equals(object obj){
// Use 'as' rather than a cast to get a null rather an exception            
// if the object isn't convertible           .
Person person = obj as Person;            
return this.Equals(person);        // wrong
this.FirstName.Equals(person.FirstName)
this.LastName.Equals(person.LastName)
// and so on
}

Je dirais, ne pas appeler 'this.Resident.Equals (other.Resident));'

Plus d'une personne peut vivre à une vérification d'adresse si le résident est faux. Une adresse est une adresse peu importe qui vit là-bas!

Sans connaître votre domaine, il est difficile de confirmer, mais la définition de l'égalité entre les deux parents, en fonction de leur relation pour les enfants de nouveau à eux semble un peu mauvais!

Vos parents ont vraiment aucun moyen de s'identifier sans vérifier leurs enfants? Vos enfants ont vraiment un ID unique, et d'eux-mêmes, ou sont-ils vraiment définis par leurs parents et de sa relation avec leurs frères et sœurs?

Si vous avez une sorte de hiérarchie unique qui est seulement unique en raison de ses relations, je suggère vos tests d'égalité devraient récursif à la racine, et faire un contrôle d'égalité fondée sur la relation de l'arbre lui-même.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top