检查对象列表的差异

时间:2015-03-16 14:06:38

标签: c#

我有以下两个清单:

List<MyObject> oldList = new List<MyObject>();
oldList.Add(new MyObject { Id = 1, Name = "hello" });
oldList.Add(New MyObject { Id = 2, Name = "world" });

List<MyObject> newList = new List<MyObject>();
newList.Add(new MyObject { Id = 1, Name = "Hello" });
newList.Add(new MyObject { Id = 3, Name = "World" });
newList.Add(new MyObject { Id = 4, Name = "hello" });

我想写一些东西来比较两个列表,如果列表不同,则返回布尔值。

例如,上述列表在以下方面有所不同:

  • 我不完全匹配
  • 计数不匹配
  • 如果ID匹配,则名称不匹配(区分大小写)

我尝试了以下内容:

if (oldList != newList)

if (!oldList.SequenceEqual(newList))

但是,两者都会产生不准确的结果。我知道我可以创建一个类IEqualityComparer的类来实现HashSet比较;但是我也读到它可能对我的情况不起作用......有人可以说明如何比较两个对象来检测我指定的变化类型吗?

1 个答案:

答案 0 :(得分:1)

正如您所说,您只需在IEquatable<MyObject>中实施正确的MyObject界面,或实施IEqualityComparer<MyObject>

/// <summary>
/// Fully equatable MyObject
/// </summary>
public class MyObject : IEquatable<MyObject>
{
    public int Id { get; set; }
    public string Name { get; set; }

    public override bool Equals(object obj)
    {
        // obj is object, so we can use its == operator
        if (obj == null)
        {
            return false;
        }

        MyObject other = obj as MyObject;

        if (object.ReferenceEquals(other, null))
        {
            return false;
        }

        return this.InnerEquals(other);
    }

    public bool Equals(MyObject other)
    {
        if (object.ReferenceEquals(other, null))
        {
            return false;
        }

        return this.InnerEquals(other);
    }

    private bool InnerEquals(MyObject other)
    {
        // Here we know that other != null;

        if (object.ReferenceEquals(this, other))
        {
            return true;
        }

        return this.Id == other.Id && this.Name == other.Name;
    }

    public override int GetHashCode()
    {
        unchecked
        {
            // From http://stackoverflow.com/a/263416/613130
            int hash = 17;
            hash = hash * 23 + this.Id.GetHashCode();
            hash = hash * 23 + (this.Name != null ? this.Name.GetHashCode() : 0);
            return hash;
        }
    }
}

然后你可以使用

if (!oldList.SequenceEqual(newList))

请注意,此比较元素顺序!如果更改元素顺序,则比较将返回false

或者您可以使用&#34;外部&#34; IEqualityComparer<MyObject>

public class MyObjectEqualityComparer : IEqualityComparer<MyObject>
{
    public static readonly MyObjectEqualityComparer Default = new MyObjectEqualityComparer();

    protected MyObjectEqualityComparer()
    {
    }

    public bool Equals(MyObject x, MyObject y)
    {
        if (object.ReferenceEquals(x, null))
        {
            return object.ReferenceEquals(y, null);
        }

        if (object.ReferenceEquals(y, null))
        {
            return false;
        }

        // Here we know that x != null && y != null;

        if (object.ReferenceEquals(x, y))
        {
            return true;
        }

        return x.Id == y.Id && x.Name == y.Name;
    }

    public int GetHashCode(MyObject obj)
    {
        if (obj == null)
        {
            return 0;
        }

        unchecked
        {
            // From http://stackoverflow.com/a/263416/613130
            int hash = 17;
            hash = hash * 23 + obj.Id.GetHashCode();
            hash = hash * 23 + (obj.Name != null ? obj.Name.GetHashCode() : 0);
            return hash;
        }
    }
}

一样使用它
if (!oldList.SequenceEqual(newList, MyObjectEqualityComparer.Default))

请注意,关于如何编写相等比较器有各种各样的思想,并且有一点需要注意:如果你覆盖operator==,你必须非常警惕不在比较器中使用它: - )

operator==重载时错误代码的典型示例

public bool Equals(MyObject other)
{
    // BOOOM!!! StackOverflowException!
    // Equals will call operator== that will probably call
    // Equals back! and so on and so on.
    if (other == null)
    {
        return false;
    }

    return this.InnerEquals(other);
}

所以最好做object.ReferenceEquals(something, someotherthing)做参考比较。

然后有null处理属性的问题:

Name(a string)的哈希值如下:

hash = hash * 23 + obj.Name != null ? obj.Name.GetHashCode() : 0

因此如果obj.Namenull,则代码不会在NullReferenceException中进行展示。匿名对象的自动生成代码使用另一种方式:

hash = hash * 23 + EqualityComparer<string>.Default.GetHashCode(obj.Name);

EqualityComparer<string>.Default即使使用null值也可以安全使用。

对于Equals属性的比较,匿名对象的自动生成代码使用了另一个有趣的技巧:

&& EqualityComparer<string>.Default.Equals(this.Name, obj.Name);

所以它使用EqualityComparer<string>.Default.Equals,然后正确使用各种方法/接口来比较对象。