我有一些包含许多简单属性的类(来自我无法控制的数据模型) - 我希望能够找到对象的新版本是否与旧版本相同,但是不想做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.
}
答案 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;
}
}
}