c# – Value-equals和循环引用:如何解决无限递归?

前端之家收集整理的这篇文章主要介绍了c# – Value-equals和循环引用:如何解决无限递归?前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
我有一些包含几个字段的类.我需要按值比较它们,即如果它们的字段包含相同的数据,则类的两个实例是相等的.我为此重写了GetHashCode和Equals方法.

可能会发生这些类包含循环引用.

示例:我们想要建立机构(如政府,体育俱乐部等).一个机构有一个名字.俱乐部是一个拥有名称和成员名单的机构.每个成员都是具有姓名和最喜欢的机构的人.如果某个俱乐部的成员将该俱乐部作为他最喜欢的机构,我们有一个循环参考.

但循环引用与值相等一起导致无限递归.这是一个代码示例:

interface IInstitution { string Name { get; } }

class Club : IInstitution
{
    public string Name { get; set; }
    public HashSet<Person> Members { get; set; }

    public override int GetHashCode() { return Name.GetHashCode() + Members.Count; }

    public override bool Equals(object obj)
    {
        Club other = obj as Club;
        if (other == null)
            return false;

        return Name.Equals(other.Name) && Members.SetEquals(other.Members);
    }
}

class Person
{
    public string Name { get; set; }
    public IInstitution FavouriteInstitution { get; set; }

    public override int GetHashCode() { return Name.GetHashCode(); }

    public override bool Equals(object obj)
    {
        Person other = obj as Person;
        if (other == null)
            return false;

        return Name.Equals(other.Name)
            && FavouriteInstitution.Equals(other.FavouriteInstitution);
    }
}

class Program
{
    public static void Main()
    {
        Club c1 = new Club { Name = "myClub",Members = new HashSet<Person>() };
        Person p1 = new Person { Name = "Johnny",FavouriteInstitution = c1 }
        c1.Members.Add(p1);

        Club c2 = new Club { Name = "myClub",Members = new HashSet<Person>() };
        Person p2 = new Person { Name = "Johnny",FavouriteInstitution = c2 }
        c2.Members.Add(p2);

        bool c1_and_c2_equal = c1.Equals(c2); // StackOverflowException!
            // c1.Equals(c2) calls Members.SetEquals(other.Members)
            // Members.SetEquals(other.Members) calls p1.Equals(p2)
            // p1.Equals(p2) calls c1.Equals(c2) 
    }
}

c1_and_c2_equal应该返回true,实际上我们(人类)可以通过一点点思考看到它们是相等的,而不会遇到无限递归.但是,我无法真实地说出我们如何解决这个问题.但既然有可能,我希望有一种方法可以在代码解决这个问题!

所以问题是:如何在不进行无限递归的情况下检查值的相等性?

请注意,我需要解决一般的循环引用,而不仅仅是上面的情况.我将其称为2圈,因为c1引用p1,p1引用c1.可以存在其他n圆圈,例如如果俱乐部A有一个成员M,其中最喜欢的是俱乐部B,其中有成员N,其最喜欢的俱乐部是A.这将是一个4圈.其他对象模型也可能允许具有奇数n的n个圆.我正在寻找一种方法来立即解决所有这些问题,因为我事先不知道n可以具有哪个值.

解决方法

一个简单的解决方法(在RDBMS中使用)是使用唯一的Id来标识Person(任何类型).然后你不需要比较所有其他属性,你永远不会遇到这样的cuircular引用.

另一种方法是在Equals中进行不同的比较,因此仅对Equals的类型提供深度检查,而不是对引用的类型进行深度检查.您可以使用自定义比较器:

public class PersonNameComparer : IEqualityComparer<Person>
{
    public bool Equals(Person x,Person y)
    {
        if (x == null && y == null) return true;
        if (x == null || y == null) return false;
        if(object.ReferenceEquals(x,y)) return true;
        return x.Name == y.Name;
    }

    public int GetHashCode(Person obj)
    {
        return obj?.Name?.GetHashCode() ?? int.MinValue;
    }
}

现在你可以改变俱乐部的Equals实施,以避免会员(人)使用他们的深层检查,其中包括机构,但只有他们的名字:

public override bool Equals(object obj)
{
    if (Object.ReferenceEquals(this,obj))
        return true;

    Club other = obj as Club;
    if (other == null)
        return false;

    var personNameComparer = new PersonNameComparer();
    return Name.Equals(other.Name) 
        && Members.Count == other.Members.Count 
        && !Members.Except(other.Members,personNameComparer).Any();
}

您注意到我无法使用SetEquals,因为我的自定义比较器没有过载.

猜你在找的C#相关文章