C # IsEqual со списком игнорируемых - PullRequest
0 голосов
/ 15 ноября 2011

У меня есть несколько классов с множеством простых свойств (из модели данных, которые я не могу контролировать) - я хотел бы узнать, совпадает ли новая версия объекта со старой версией, но не не хочу использовать 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 ]

1 голос
/ 15 ноября 2011
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;
}
1 голос
/ 15 ноября 2011

Вот как мы этого добиваемся в Umbraco Framework, с базовым классом AbstractEquatableObject, который является модифицированной версией BaseObject архитектуры Sharp http://umbraco.codeplex.com/SourceControl/changeset/view/2b4d693de19c#Source%2fLibraries%2fUmbraco.Framework%2fAbstractEquatableObject.cs

Переопределители реализации GetMembersForEqualityComparison(), и базовый класс кэширует объекты PropertyInfoодин раз для каждого типа для приложения в ConcurrentDictionary<Type, IEnumerable<PropertyInfo>>.

Я вставил класс сюда, хотя он ссылается на LogHelper в другом месте в Framework, так что вы можете удалить это (или просто использовать нашу библиотеку Framework, есть другиеполезные вещи).

Если вам нужен помощник для получения PropertyInfo из выражения, чтобы избежать магических строк повсеместно (например, заменить на x => x.MyProperty), ознакомьтесь с методами GetPropertyInfo нашего ExpressionHelperв http://umbraco.codeplex.com/SourceControl/changeset/view/2b4d693de19c#Source%2fLibraries%2fUmbraco.Framework%2fExpressionHelper.cs

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;
        }
    }
}
1 голос
/ 15 ноября 2011

Вот что-то в этом духе из нашей базы кода. Для сравнения используется список свойств, а не список свойств, которые следует игнорировать. Затем он возвращает список свойств, которые не совпадают:

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, когда вы найдете несоответствующие свойства.

Надеюсь, это поможет!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...