比较C#中具有自定义类型的两个对象

时间:2019-03-03 01:40:35

标签: c# comparison iequalitycomparer icomparer

我想比较两个具有自定义类型的对象,并返回具有差异的数据。我在Equals中覆盖了GetHashCodeAddress class并实现了ValueComparer,但是下面的代码返回了所有数据。请在下面查看预期结果。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp2
{
    public class Person
    {
        public string FirstName { get; set; }
        public string Lastname { get; set; }
        public int Id { get; set; }

        public List<Address> Address { get; set; }


    }

    public class Address:IEquatable<Address>
    {
        public string City { get; set; }
        public string Country { get; set; }
        public int Id { get; internal set; }
        public int PersonId { get; set; }

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

            Address a = obj as Address;
            if (object.ReferenceEquals(a, null))
                return false;

            return (a.City.Equals(City));
        }

        public virtual bool Equals(Address other)
        {
            if (ReferenceEquals(null, other)) return false;
            if (ReferenceEquals(this, other)) return true;
            return other.City == City;
        }
        public override int GetHashCode()
        {
            return City.GetHashCode();
        }
    }


    public class ValueComparer : IEqualityComparer<Person>
    {
        public int GetHashCode(Person co)
        {
            if (co == null)
            {
                return 0;
            }
            return co.FirstName.GetHashCode();
        }

        public bool Equals(Person x1, Person x2)
        {
            if (object.ReferenceEquals(x1, x2))
            {
                return true;
            }
            if (object.ReferenceEquals(x1, null) ||
                object.ReferenceEquals(x2, null))
            {
                return false;
            }
            return x1.FirstName.Equals(x2.FirstName)
                && x1.Address.Equals(x2.Address);
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            var personsOrig = new List<Person>
                     {
                         new Person {Id=1, FirstName = "John", Lastname = "Bill"
                                    , Address=new List<Address>{new Address {Id=1, City = "New York", Country = "US", PersonId=1 }
                                                               ,new Address {Id=2, City = "Okinawa", Country = "Japan", PersonId=1 }
                                                                },

                         }
                         ,new Person {Id=2, FirstName = "Mary", Lastname = "Doe"
                                    , Address=new List<Address>{new Address {Id=3, City = "Los Angeles", Country = "US", PersonId=2 }
                                                                } }
                         ,new Person {Id=3, FirstName = "Joe", Lastname = "McDonalds"
                                    , Address=new List<Address>{new Address {Id=4, City = "California", Country = "US", PersonId=3 }
                                                                } }
                         ,new Person {Id=4, FirstName = "Donald", Lastname = "Gates"
                                    , Address=new List<Address>{new Address {Id=5, City = "San Francisco", Country = "US", PersonId=4 }
                                                                } }
                         ,new Person {Id=5, FirstName = "Shawn", Lastname = "Porter"
                                    , Address=new List<Address>{new Address {Id=6, City = "Utah", Country = "US", PersonId=5 }
                                                                } }
            };
            var personsNew = new List<Person>
                     {
                         new Person {Id=1, FirstName = "John", Lastname = "Bill"
                                    , Address=new List<Address>{
                                                                    new Address { Id = 1, City = "New York", Country = "US", PersonId = 1 }
                                                                    }
                         }
                        ,new Person {Id=2, FirstName = "Mary", Lastname = "Doe"
                                    , Address=new List<Address>{new Address {Id=7, City = "Florida", Country = "US", PersonId=2 } 
                                                                } }
                        ,new Person {Id=3, FirstName = "Joe", Lastname = "Jean"
                                    , Address=new List<Address>{new Address {Id=4, City = "California", Country = "US", PersonId=3}
                                                                } }
                        ,new Person {Id=4, FirstName = "Donald", Lastname = "Gates"
                                    , Address=new List<Address>{new Address {Id=5, City = "San Francisco", Country = "US", PersonId=4 }
                                                                } }
                        ,new Person {Id=5, FirstName = "Shawn", Lastname = "Porter"
                                    , Address=new List<Address>{new Address {Id=6, City = "Utah", Country = "US", PersonId=5 }
                                                                } }
            };



            var xxx = personsNew.Except(personsOrig,new ValueComparer()).ToList();

        }
    }
}

预期产量

John Bill
Mary Doe
Joe McDonalds

2 个答案:

答案 0 :(得分:1)

您正在比较Person对象的Address字段,该字段是Address对象的列表。因此,不会调用Address equals方法-而是在比较每个Person的地址列表(为了清楚起见,我建议将其重命名为lstAddresses),而无需建立任何逻辑来覆盖两个列表对象的默认比较。

答案 1 :(得分:0)

我最近不得不这样做,并实现了自定义的EqualityComparer。 它递归地检查属性及其值。

public class StructuralEqualityComparer : IEqualityComparer
{
    private readonly Func<PropertyInfo, bool> _excludePredicate;
    private readonly StructuralEqualityComparerOptions _options;

    public StructuralEqualityComparer(Func<PropertyInfo, bool> excludePredicate = null, StructuralEqualityComparerOptions options = null)
    {
        Func<PropertyInfo, bool> defaultExcludePredicate = (propertyInfo) => false;
        _excludePredicate = excludePredicate ?? defaultExcludePredicate;
        _options = options;
    }

    public new bool Equals(object x, object y)
    {
        if (x == null)
            return y == null;
        if (y == null)
            return false;

        var bindingFlags = BindingFlags.Public | BindingFlags.Instance;
        var xProperties = x.GetType().GetProperties(bindingFlags);
        var xPropertiesToCompare = xProperties
            .Except(xProperties.Where(_excludePredicate))
            .OrderBy(propertyInfo => propertyInfo.Name);

        var yProperties = y.GetType().GetProperties(bindingFlags);
        var yPropertiesToCompare = yProperties
            .Except(yProperties.Where(_excludePredicate))
            .OrderBy(propertyInfo => propertyInfo.Name);

        Func<object, object, bool> equalityPredicate = (value1, value2) => PropertyEquals(x, y, value1 as PropertyInfo, value2 as PropertyInfo);
        return SequenceEquals(xPropertiesToCompare, yPropertiesToCompare, equalityPredicate);
    }

    private bool SequenceEquals(IEnumerable first, IEnumerable second, Func<object, object, bool> equalityPredicate)
    {
        var firstEnumerator = first.GetEnumerator();
        var secondEnumerator = second.GetEnumerator();
        {
            while (firstEnumerator.MoveNext())
            {
                if (!secondEnumerator.MoveNext())
                    return false;
                if (!equalityPredicate(firstEnumerator.Current, secondEnumerator.Current))
                    return false;
            }

            if (secondEnumerator.MoveNext())
                return false;
        }

        return true;
    }

    public int GetHashCode(object obj) => obj.GetHashCode();

    private bool PropertyEquals(object x, object y, PropertyInfo xPropertyInfo, PropertyInfo yPropertyInfo)
    {
        if (xPropertyInfo == null)
            return yPropertyInfo == null;
        if (yPropertyInfo == null)
            return false;
        if (_options.NameCompare)
        {
            if (xPropertyInfo.Name != yPropertyInfo.Name)
                return false;
        }
        if (_options.TwoWayAssignableCompare)
        {
            if (!xPropertyInfo.PropertyType.IsAssignableFrom(yPropertyInfo.PropertyType) || !yPropertyInfo.PropertyType.IsAssignableFrom(xPropertyInfo.PropertyType))
                return false;
        }
        else if (_options.OneWayAssignableCompare)
        {
            if (!xPropertyInfo.PropertyType.IsAssignableFrom(yPropertyInfo.PropertyType))
                return false;
        }
        if (_options.ValueCompare)
        {    
            var xValue = xPropertyInfo.GetValue(x);
            var yValue = yPropertyInfo.GetValue(y);

            if (xValue is IEnumerable || yValue is IEnumerable)
            {
                return SequenceEquals(xValue as IEnumerable, yValue as IEnumerable, _options.EqualityComparer.Equals);
            }

            if (_options.EqualityComparer.Equals(xValue, yValue) || _options.EqualityComparer.Equals(yValue, xValue))
                return true;

            if (_options.RecursiveCompare && (xValue != x || yValue != y))
            {
                return Equals(xValue, yValue);
            }
        }
        return true;
    }
}

public class StructuralEqualityComparerOptions
{
    /// <summary>
    /// Check that the names of the properties match
    /// </summary>
    public bool NameCompare { get; set; } = true;
    /// <summary>
    /// Check that the Type of TY can be assigned to TX.
    /// </summary>
    public bool OneWayAssignableCompare { get; set; } = true;
    /// <summary>
    /// Check that the Type of TY can be assigned to TX and vise versa, skips OneWayAssignableCompare.
    /// </summary>
    public bool TwoWayAssignableCompare { get; set; } = false;
    /// <summary>
    /// Check that the values of the properties match, using EqualityComparer
    /// </summary>
    public bool ValueCompare { get; set; } = true;
    /// <summary>
    /// Recursively walk for nested types
    /// </summary>
    public bool RecursiveCompare { get; set; } = true;
    /// <summary>
    /// The IEqualityComparer to use for ValueCompare
    /// </summary>
    public IEqualityComparer EqualityComparer { get; set; } = EqualityComparer<object>.Default;
}