LINQ.Distinct 在 IEquatable 对象上不起作用

LINQ.Distinct on IEquatable object doesn't work

我有一个继承自 IEquatable<> 的基础 class 的对象。到目前为止一切顺利,它适用于继承相同基础 class 的其他对象。但是我在 class "RoomType" 上使用 "Attrbiutes" 属性 时似乎有问题。下面你会看到 classes 和一个测试,我希望给出其他输出。

我通过 RoomType.GetHashCode() 将问题缩小到某些问题,当我评论 "SafeHashCode(Attributes)" 时返回了预期的结果。

测试:

 private static void QuickTest()
    {
        RoomType[] rooms = new RoomType[] {
            new RoomType {
                Attributes = new [] { "a", "b,"c"},
            },
            new RoomType
            {
                Attributes = new [] { "a", "b","c"},
            }
        };

        List<RoomType> result = rooms.Distinct().ToList();
        //result contains 2 items, I was expecting 1
    }

房间类型:

public class RoomType : EntityBase
{
    public string OriginalRoomCode { get; set; }
    public Enum.RoomType RoomCode { get; set; }
    public IEnumerable<string> Attributes { get; set; }

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

    public override bool Equals(EntityBase obj)
    {
        RoomType y = (RoomType)obj;
        return SafeEqual(OriginalRoomCode, y.OriginalRoomCode) &&
            SafeEqual(RoomCode, y.RoomCode) &&
            SafeEqual(Attributes,y.Attributes);
    }

    public override int GetHashCode()
    {
        unchecked
        {
            return SafeHashCode(OriginalRoomCode) ^
                   SafeHashCode(RoomCode) ^ 
                   SafeHashCode(Attributes);
        }
    }

    public override object Clone()
    {
        return new RoomType
        {
            RoomCode = (Enum.RoomType)SafeClone(RoomCode),
            OriginalRoomCode = (string)SafeClone(OriginalRoomCode),
            Attributes = (IEnumerable<string>)SafeClone(Attributes)
        };
    }
}

实体库:

public abstract class EntityBase : IEquatable<EntityBase>, ICloneable
{
    public bool SafeEqual<T>(T x, T y)
    {
        bool isXDefault = EqualityComparer<T>.Default.Equals(x, default(T));
        bool isYDefault = EqualityComparer<T>.Default.Equals(y, default(T));

        if (isXDefault && isYDefault)
            return true;
        if (isXDefault != isYDefault)
            return false;

        if (x is EntityBase)
            return x.Equals(y);

        IEnumerable<object> xEnumerable = x as IEnumerable<object>;
        IEnumerable<object> yEnumerable = y as IEnumerable<object>;

        if (xEnumerable != null && yEnumerable != null)
        {
            foreach (var yItem in yEnumerable)
            {
                bool match = false;
                foreach (var xItem in xEnumerable)
                {
                    if(SafeEqual(xItem, yItem))
                    {
                        match = true;
                        break;
                    }                        
                }
                if (!match)
                    return false;
            }
            return true;
        }

        return x.Equals(y);
    }

    public int SafeHashCode<T>(T x)
    {
        if (EqualityComparer<T>.Default.Equals(x, default(T)))
            return 0;



        return x.GetHashCode();
    }

    public object SafeClone<T>(T x)
    {
        //if x is null or default value
        if (EqualityComparer<T>.Default.Equals(x, default(T)))
            return default(T);

        //if x is of type EntityBase call clone()
        if (x is EntityBase)
            return (x as EntityBase).Clone();

        //else the type is a default type return the value
        return x;
    }

    public abstract bool Equals(EntityBase other);
    public override abstract int GetHashCode();

    public abstract override bool Equals(object obj);

    public abstract object Clone();
}

更新 我能够通过在 SafeHashCode(T x)

中添加以下代码来修复它
 IEnumerable<object> xEnumerable = x as IEnumerable<object>;
        if (xEnumerable != null)
            return xEnumerable.Aggregate(17, (acc, item) => acc * 19 + SafeHashCode(item));

对于 SaveEqual,您可以自定义检查 IEnumerable 类型比较 - 您可以检查 xEnumerable 中的每个项目是否包含在 yEnumerable 中。 注意 1:您这里也有一个错误 - yEnumerable 可以包含其他项目或者它可以有重复的项目。

但是对于 SaveHashCode,您没有对 IEnumerable 类型的自定义处理。你只是 return 参数的哈希码。这将为不同的数组实例提供不同的结果,即使数组包含相同的值也是如此。要解决这个问题,您应该根据集合项计算哈希码:

public int SaveHashCode<T>(T x)
{
    if (EqualityComparer<T>.Default.Equals(x, default(T)))
        return 0;

    IEnumerable<object> xEnumerable = x as IEnumerable<object>;
    if (xEnumerable != null)
         return xEnumerable.Aggregate(17, (acc, item) => acc * 19 + SaveHashCode(item));

    return x.GetHashCode();
}

注 2 使用 XOR 进行 hashCode 计算会给您带来另一个问题 - 与零进行异或(您将其用作默认值,或者它可以是整数的哈希码 0, or boolean false) 不修改结果。结果也不会取决于订单。 IE。如果你有两个数组:[0, 1, 2][2,0,1,0]。按照@christophano 的建议进行异或运算会给你……33。但是那些数组是完全不同的。所以我建议你使用 hashCode calcualtion based on prime numbers.

问题是 2 个数组的哈希码将不相同,即使内容可以被认为是相同的。 而不是将数组传递给 SafeHashCode 获取数组每个成员的哈希码。

public override int GetHashCode()
{
    unchecked
    {
        return SafeHashCode(OriginalRoomCode) ^
               SafeHashCode(RoomCode) ^ 
               Attributes.Select(x => SafeGetHashCode(x)).Aggregate((seed, current) => seed ^ current);
    }
}