对IEquatable对象的LINQ.Distinct不起作用

时间:2017-04-05 13:19:47

标签: c# linq iequatable

我从一个继承自IEquatable<>的基类继承的对象。到目前为止这么好,它适用于继承相同基类的其他对象。但是我上课了#34; RoomType"当我使用" Attrbiutes"时似乎有问题。属性。下面你会看到我希望得到其他输出的类和测试。

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

测试:

 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
    }

RoomType:

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

EntityBase:

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

2 个答案:

答案 0 :(得分:2)

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

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

答案 1 :(得分:1)

对于SaveEqual,您可以自定检查IEnumerable类型比较 - 您可以检查xEnumerable中的每个项目是否包含在yEnumerable中。 注1 :此处也有错误 - yEnumerable可以包含其他项目,也可能包含重复项目。

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

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计算会给您带来另一个问题 - XORing为零(您使用的是默认值,或者它可以是整数0的哈希码,或者是布尔值false)不会修改结果。结果也不取决于订单。即如果您有两个数组:[0, 1, 2][2,0,1,0]。在@christophano建议的XORing会给你...... 33。但那些阵列完全不同。所以我建议你使用hashCode计算based on prime numbers