代码之家  ›  专栏  ›  技术社区  ›  Zach Burlingame

C语言中具有双向关联的值相等#

  •  4
  • Zach Burlingame  · 技术社区  · 17 年前

    出身背景

    在我正在进行的一个C#项目中,我有两个对象,它们之间有双向关联。由于许多原因(例如在集合中使用它们),我需要能够检查值相等(与引用相等),因此我正在实现IEquatable和相关函数。

    假设

    任何解决方案都必须:

    • 允许双向关联保持不变,同时允许检查值是否相等。
    • 允许该类的外部使用从IEquatable调用Equals(Object obj)或Equals(T class),并接收正确的行为(例如在System.Collections.Generic中)。

    类似地,在GetHashCode计算中使用类的所有字段将导致类似的无限递归并导致堆栈溢出问题。


    问题

    如何在不导致堆栈溢出的情况下检查具有双向关联的两个对象之间的值相等性?


    注:

    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();
            }
        }
    }
    
    5 回复  |  直到 17 年前
        1
  •  2
  •   Mehrdad Afshari    17 年前

    您将类耦合得太紧,并且混合了值和引用。您应该考虑检查一个类的引用相等性,或者让它们知道彼此(通过提供 internal 专业的 Equals 方法或手动检查其他类的值相等性)。这应该不是什么大问题,因为您的需求明确要求这种耦合,所以您不会通过这样做来引入耦合。

        2
  •  1
  •   Zach Burlingame    17 年前

    如果可以重新设计类结构以删除双向关联,并减少与实现相关的问题数量,那么这是首选解决方案。

    如果这种重新设计不可能,或者引入了相等或更大的实现问题,那么一种可能的解决方案是使用专用的Equals方法,由双向关联中涉及的类的Equals方法调用。正如Mehrdad所说,这不应该是一个太大的问题,因为需求明确要求这种耦合,所以您不能通过这样做来引入一种耦合。


    这是一个实现,它使专门的方法只检查它们自己的字段。与让每个类对另一个类的每个属性进行比较相比,这减少了维护问题。

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

    输出

    这两个地址相等。

        3
  •  0
  •   Community Mohan Dere    6 年前

    我认为最好的解决方案是将Address类分为两部分

    那么在Person类中比较核心地址信息就相当简单了,而无需创建SO。

    是的,这确实在您的代码中创建了一点耦合,因为人们现在对OccuppiedAddress如何工作有了一点内在的了解。但是这些类已经有了紧密的耦合,所以实际上您并没有使问题变得更糟。

        4
  •  -1
  •   Eric    17 年前
    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
    }
    
        5
  •  -1
  •   Noel Kennedy    17 年前

    我会说,不要叫‘this.Resident.Equals(other.Resident));’

    不止一个人可以住在一个地址,因此检查住户是错误的。一个地址就是一个地址,不管谁住在那里!

    在不知道你的领域的情况下,很难证实这一点,但基于他们孩子的关系来定义父母之间的平等似乎有点难闻!

    你的父母真的没有办法在不检查孩子的情况下确定自己的身份吗?你的孩子是否真的有一个独一无二的ID,或者他们真的是由父母及其与兄弟姐妹的关系定义的?