c# - C# 中双向关联的值相等

标签 c# .net equals iequatable

背景

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

假设

  • 我使用的是 C# 3.0、.NET 3.5 和 Visual Studio 2008(尽管相等比较例程问题应该无关紧要)。

约束

任何解决方案都必须:

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

问题

当实现 IEquatable 以检查具有双向关联的类型的值相等性时,会发生无限递归,从而导致堆栈溢出。

注意:类似地,在 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();
        }
    }
}

最佳答案

您将类耦合得太紧密并且混合了值和引用。您应该考虑检查其中一个类的引用相等性或让它们相互了解(通过为特定类提供 internal 专门的 Equals 方法或手动检查值相等性另一类)。这应该没什么大不了的,因为您的要求明确要求这种耦合,所以您不会通过这样做引入耦合。

关于c# - C# 中双向关联的值相等,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/731206/

相关文章:

c# - ASP.NET Core 属性路由

c# - 在 C# 中创建深克隆和浅克隆有更好的方法吗?

c# - SaveFileDialog 设置默认路径和文件类型?

Java : Equals() does not compare right

Bash 脚本 if -eq

java - 两个看似相等的字符串并不相等

c# - 通过遍历数组来归档每个字符串

c# - 使用 Unity 时配置 WCF 客户端的 MaxItemsInObjectGraph

c# - 如何导入文件名为 "improper"的 CSV 文件?

c# - 没有注册类型 'MyType' 的服务