C#IsEqual带有可忽略的列表

时间:2011-11-14 23:33:33

标签: c# generics compare

我有一些包含许多简单属性的类(来自我无法控制的数据模型) - 我希望能够找到对象的新版本是否与旧版本相同,但是不想做20个不同的" IsEqual"方法(我不喜欢" IsEqual"名称,因为它不是==的模拟)。另一个皱纹,在大多数情况下,我不希望它做一个深刻的比较,但在某些情况下,我确实想要这样做。

我喜欢以下内容:

//Property could be PropertyInfo if that is necessary
bool IsEqual<T>(T first, T second, List<Property> ignorableProperties=emptyList, bool recurse=false)
{
    //the comparison code returning if they are equal ignoring 
    //the properties in the ignorableProperties list, recursing if recurse == true
    //not sure how I'd handle the comparison of sub-objects in the recursive step.
}

3 个答案:

答案 0 :(得分:1)

以下是我们代码库中的这些内容。它使用要比较的属性列表,而不是要忽略的属性列表。然后它返回一个列表,列出哪些属性不匹配:

public static List<PropertyInfo> CompareObjects<T>(T o1, T o2, List<PropertyInfo> props) where T : class
{
    var type = typeof(T);
    var mismatched = CompareObjects(type, o1, o2, props);
    return mismatched;
}

public static List<PropertyInfo> CompareObjects(Type t, object o1, object o2, List<PropertyInfo> props)
{
    List<PropertyInfo> mismatched = null;
    foreach (PropertyInfo prop in props)
    {
        if (prop.GetValue(o1, null) == null && prop.GetValue(o2, null) == null) ;
        else if (
            prop.GetValue(o1, null) == null || prop.GetValue(o2, null) == null ||
                !prop.GetValue(o1, null).Equals(prop.GetValue(o2, null)))
        {
            if (mismatched == null) mismatched = new List<PropertyInfo>();
            mismatched.Add(prop);
        }
    }
    return mismatched;
}

如果你想要一个IsEqual方法,那么当你找到不匹配的属性时,就会返回true / false

希望这有帮助!

答案 1 :(得分:1)

public static bool AreEqual<T>(this T first, T second, 
  bool recurse = false, params string[] propertiesToSkip)
{
  if (Equals(first, second)) return true;

  if (first == null)
    return second == null;
  else if (second == null)
    return false;

  if (propertiesToSkip == null) propertiesToSkip = new string[] { };
  var properties = from t in first.GetType().GetProperties()
                   where t.CanRead
                   select t;

  foreach (var property in properties)
  {
    if (propertiesToSkip.Contains(property.Name)) continue;

    var v1 = property.GetValue(first, null);
    var v2 = property.GetValue(second, null);

    if (recurse)
      if (!AreEqual(v1, v2, true, propertiesToSkip))
        return false;
      else
        continue;

    if (!Equals(v1, v2)) return false;
  }
  return true;
}

答案 2 :(得分:1)

以下是我们如何在Umbraco框架中实现这一点,其中包含一个名为AbstractEquatableObject的基类,它是Sharp Architecture的BaseObject http://umbraco.codeplex.com/SourceControl/changeset/view/2b4d693de19c#Source%2fLibraries%2fUmbraco.Framework%2fAbstractEquatableObject.cs的修改版本

实现者覆盖GetMembersForEqualityComparison(),并且基类在ConcurrentDictionary<Type, IEnumerable<PropertyInfo>>中为每个类型缓存一次PropertyInfo对象。

我在这里粘贴了这个类,虽然它引用了Framework中其他地方的LogHelper,所以你可以删除它(或者只使用我们的Framework lib,其中有其他有用的东西)。

如果你想从表达式获取PropertyInfo的帮助器,为了避免遍布整个地方的魔术字符串(例如替换为x => x.MyProperty),请在http://umbraco.codeplex.com/SourceControl/changeset/view/2b4d693de19c#Source%2fLibraries%2fUmbraco.Framework%2fExpressionHelper.cs <查看ExpressionHelper的GetPropertyInfo方法/ p>

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Umbraco.Framework.Diagnostics;

namespace Umbraco.Framework
{
    /// <summary>
    /// Objects implementing <see cref="AbstractEquatableObject{T}"/> are provided with common functionality for establishing domain-specific equality
    /// and a robust implementation of GetHashCode
    /// </summary>
    /// <typeparam name="T"></typeparam>
    [Serializable]
    public abstract class AbstractEquatableObject<T> where T : AbstractEquatableObject<T>
    {
        /// <summary>
        /// Returns the real type in case the <see cref="object.GetType" /> method has been proxied.
        /// </summary>
        /// <returns></returns>
        /// <remarks></remarks>
        protected internal virtual Type GetNativeType()
        {
            // Returns the real type in case the GetType method has been proxied
            // See http://groups.google.com/group/sharp-architecture/browse_thread/thread/ddd05f9baede023a for clarification
            return GetType();
        }

        /// <summary>Returns a hash code for this instance.</summary>
        /// <returns>A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. </returns>
        public override int GetHashCode()
        {
            unchecked
            {
                // Based on an algorithm set out at http://sharp-architecture.googlecode.com/svn/trunk/src/SharpArch/SharpArch.Core/DomainModel/BaseObject.cs

                var naturalIdMembers = EnsureEqualityComparisonMembersCached();

                // It's possible for two objects to return the same hash code based on 
                // identically valued properties, even if they're of two different types, 
                // so we include the object's type in the hash calculation
                var hashCode = GetType().GetHashCode();

                if (!naturalIdMembers.Any()) return base.GetHashCode();

                foreach (var value in naturalIdMembers
                    .Select(x => x.GetValue(this, null))
                    .Where(x => !ReferenceEquals(x, null)))
                {
                    // Check if the property value is null or default (e.g. Guid.Empty)
                    // In which case we just want to use the base GetHashCode because we have no other way
                    // of determining if the instances are different
                    if (value.Equals(value.GetType().GetDefaultValue()))
                        hashCode = (hashCode * 41) ^ base.GetHashCode();
                    else
                        hashCode = (hashCode * 41) ^ value.GetHashCode();
                }

                return hashCode;
            }
        }

        /// <summary>Determines whether the specified object is equal to this instance.</summary>
        /// <param name="obj">The <see cref="System.Object"/> to compare with this instance.</param>
        /// <returns><c>true</c> if the specified <see cref="System.Object"/> is equal to this instance; otherwise, <c>false</c>.</returns>
        public override bool Equals(object obj)
        {
            if (ReferenceEquals(obj, null)) return false;
            var incoming = obj as AbstractEquatableObject<T>;
            if (ReferenceEquals(incoming, null)) return false;
            if (ReferenceEquals(this, incoming)) return true;


            // (APN Oct 2011) Disabled the additional check for GetNativeType().Equals(incoming.GetNativeType())
            // so that we can compare RelationById with Relation using Equals however this may need reinstating
            // and using IComparable instead
            return CompareCustomEqualityMembers(incoming);
        }

        /// <summary>
        /// Implements the operator ==.
        /// </summary>
        /// <param name="left">The left.</param>
        /// <param name="right">The right.</param>
        /// <returns>The result of the operator.</returns>
        /// <remarks></remarks>
        public static bool operator ==(AbstractEquatableObject<T> left, AbstractEquatableObject<T> right)
        {
            // If both are null, or both are same instance, return true.
            if (ReferenceEquals(left, right)) return true;

            // If one is null, but not both, return false.
            if (((object)left == null) || ((object)right == null)) return false;

            return left.Equals(right);
        }

        /// <summary>
        /// Implements the operator !=.
        /// </summary>
        /// <param name="left">The left.</param>
        /// <param name="right">The right.</param>
        /// <returns>The result of the operator.</returns>
        /// <remarks></remarks>
        public static bool operator !=(AbstractEquatableObject<T> left, AbstractEquatableObject<T> right)
        {
            return !(left == right);
        }

        /// <summary>
        /// A static <see cref="ConcurrentDictionary{Type, IEnumerable{PropertyInfo}}"/> cache of natural ids for types which may implement this abstract class.
        /// </summary>
        protected readonly static ConcurrentDictionary<Type, IEnumerable<PropertyInfo>> EqualityComparisonMemberCache = new ConcurrentDictionary<Type, IEnumerable<PropertyInfo>>();

        /// <summary>
        /// Gets the natural id members.
        /// </summary>
        /// <returns></returns>
        /// <remarks></remarks>
        protected abstract IEnumerable<PropertyInfo> GetMembersForEqualityComparison();

        /// <summary>
        /// Ensures the natural id members are cached in the static <see cref="EqualityComparisonMemberCache"/>.
        /// </summary>
        /// <returns></returns>
        /// <remarks></remarks>
        protected internal virtual IEnumerable<PropertyInfo> EnsureEqualityComparisonMembersCached()
        {
            return EqualityComparisonMemberCache.GetOrAdd(GetNativeType(), x => GetMembersForEqualityComparison());
        }

        /// <summary>
        /// Establishes if the natural id of this instance matches that of <paramref name="compareWith"/>
        /// </summary>
        /// <param name="compareWith">The instance with which to compare.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        protected internal virtual bool CompareCustomEqualityMembers(AbstractEquatableObject<T> compareWith)
        {
            // Standard input checks - if it's the same instance, or incoming is null, etc.
            if (ReferenceEquals(this, compareWith)) return true;
            if (ReferenceEquals(compareWith, null)) return false;

            // Get the natural id spec
            var naturalIdMembers = EnsureEqualityComparisonMembersCached();

            // If the overriding objct hasn't specified a natural id, just return the base Equals implementation
            if (!naturalIdMembers.Any()) return base.Equals(compareWith);

            // We have a natural id specified, so compare the members
            foreach (var naturalIdMember in naturalIdMembers)
            {
                try
                {
                    // Get the property values of this instance and the incoming instance
                    var localValue = naturalIdMember.GetValue(this, null);
                    var incomingValue = naturalIdMember.GetValue(compareWith, null);

                    // If the property values refere to the same instance, or both refer to null, continue the loop
                    if (ReferenceEquals(localValue, incomingValue) || (ReferenceEquals(localValue, null) && ReferenceEquals(incomingValue, null)))
                        continue;

                    // If this property value doesn't equal the incoming value, the comparison fails so we can return straight away
                    if (!localValue.Equals(incomingValue)) return false;
                }
                catch (Exception ex)
                {
                    // If there was an error accessing one of the properties, log it and return false
                    LogHelper.TraceIfEnabled<AbstractEquatableObject<T>>("Error comparing {0} to {1}: {2}",
                                                                         () => GetNativeType().Name,
                                                                         () => compareWith.GetNativeType().Name,
                                                                         () => ex.Message);
                    return false;
                }
            }

            // To get this far means we haven't had any misses, so return true
            return true;
        }
    }
}