在C#中比较对象属性


111

这是我在许多其他类继承的类上作为方法提出的。这个想法是它允许在相同类型的对象的属性之间进行简单的比较。

现在,它确实可以工作-但是为了提高代码质量,我认为应该将其丢弃以进行检查。怎么会更好/更高效/等等?

/// <summary>
/// Compare property values (as strings)
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public bool PropertiesEqual(object comparisonObject)
{

    Type sourceType = this.GetType();
    Type destinationType = comparisonObject.GetType();

    if (sourceType == destinationType)
    {
        PropertyInfo[] sourceProperties = sourceType.GetProperties();
        foreach (PropertyInfo pi in sourceProperties)
        {
            if ((sourceType.GetProperty(pi.Name).GetValue(this, null) == null && destinationType.GetProperty(pi.Name).GetValue(comparisonObject, null) == null))
            {
                // if both are null, don't try to compare  (throws exception)
            }
            else if (!(sourceType.GetProperty(pi.Name).GetValue(this, null).ToString() == destinationType.GetProperty(pi.Name).GetValue(comparisonObject, null).ToString()))
            {
                // only need one property to be different to fail Equals.
                return false;
            }
        }
    }
    else
    {
        throw new ArgumentException("Comparison object must be of the same type.","comparisonObject");
    }

    return true;
}


3
顺便说一下,您知道这个SE网站:codereview.stackexchange.com
2014年

有一些对象比较库:CompareNETObjectsDeepEqualAutoCompareZCompare ...
nawfal

...和一吨普通相等比较器实现者,其中一些是:MemberwiseEqualityComparer恶趣SemanticComparisonEqualityComparerDeepEqualityComparer平等Equals.Fody。后者可能会在范围和灵活性方面受到限制,因为他们可以实现什么目标。
nawfal

我投票结束这个问题是因为题外,因为它属于 代码审查
Xiaoy312 '18

Answers:


160

我一直在寻找一段代码,这些代码可以做类似的事情来帮助编写单元测试。这是我最终使用的内容。

public static bool PublicInstancePropertiesEqual<T>(T self, T to, params string[] ignore) where T : class 
  {
     if (self != null && to != null)
     {
        Type type = typeof(T);
        List<string> ignoreList = new List<string>(ignore);
        foreach (System.Reflection.PropertyInfo pi in type.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance))
        {
           if (!ignoreList.Contains(pi.Name))
           {
              object selfValue = type.GetProperty(pi.Name).GetValue(self, null);
              object toValue = type.GetProperty(pi.Name).GetValue(to, null);

              if (selfValue != toValue && (selfValue == null || !selfValue.Equals(toValue)))
              {
                 return false;
              }
           }
        }
        return true;
     }
     return self == to;
  }

编辑:

与上面相同的代码,但使用LINQ和Extension方法:

public static bool PublicInstancePropertiesEqual<T>(this T self, T to, params string[] ignore) where T : class
{
    if (self != null && to != null)
    {
        var type = typeof(T);
        var ignoreList = new List<string>(ignore);
        var unequalProperties =
            from pi in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
            where !ignoreList.Contains(pi.Name) && pi.GetUnderlyingType().IsSimpleType() && pi.GetIndexParameters().Length == 0
            let selfValue = type.GetProperty(pi.Name).GetValue(self, null)
            let toValue = type.GetProperty(pi.Name).GetValue(to, null)
            where selfValue != toValue && (selfValue == null || !selfValue.Equals(toValue))
            select selfValue;
        return !unequalProperties.Any();
    }
    return self == to;
}

public static class TypeExtensions
   {
      /// <summary>
      /// Determine whether a type is simple (String, Decimal, DateTime, etc) 
      /// or complex (i.e. custom class with public properties and methods).
      /// </summary>
      /// <see cref="http://stackoverflow.com/questions/2442534/how-to-test-if-type-is-primitive"/>
      public static bool IsSimpleType(
         this Type type)
      {
         return
            type.IsValueType ||
            type.IsPrimitive ||
            new[]
            {
               typeof(String),
               typeof(Decimal),
               typeof(DateTime),
               typeof(DateTimeOffset),
               typeof(TimeSpan),
               typeof(Guid)
            }.Contains(type) ||
            (Convert.GetTypeCode(type) != TypeCode.Object);
      }

      public static Type GetUnderlyingType(this MemberInfo member)
      {
         switch (member.MemberType)
         {
            case MemberTypes.Event:
               return ((EventInfo)member).EventHandlerType;
            case MemberTypes.Field:
               return ((FieldInfo)member).FieldType;
            case MemberTypes.Method:
               return ((MethodInfo)member).ReturnType;
            case MemberTypes.Property:
               return ((PropertyInfo)member).PropertyType;
            default:
               throw new ArgumentException
               (
                  "Input MemberInfo must be if type EventInfo, FieldInfo, MethodInfo, or PropertyInfo"
               );
         }
      }
   }

大T-很老套,但是绝对可以用于测试和简单比较。.谢谢+1
jim tollan 2015年

1
很好,但是我发现它不适用于更复杂的对象。例如,我有一个带有一些字符串的对象(它比较它们很好),但是此对象还具有另一个对象的列表,该对象无法正确比较,因此需要以某种方式递归。
Ryan Thomas

1
我必须在第一个条件中增加两个条件,因为您必须排除在其他情况下会引发异常的索引属性。这是此错误的条件:pi.GetIndexParameters()。Length ==0。解决@RyanThomas所述问题的第二个条件是:pi.GetUnderlyingType()。IsSimpleType()。如您所见,IsSimpleType是该类Type所不存在的扩展名。我修改了答案,添加了所有这些条件和扩展名。
Samuel

64

更新: Compare-Net-Objects的最新版本位于GitHub上 ,具有NuGet包Tutorial。可以这样称呼

//This is the comparison class
CompareLogic compareLogic = new CompareLogic();

ComparisonResult result = compareLogic.Compare(person1, person2);

//These will be different, write out the differences
if (!result.AreEqual)
    Console.WriteLine(result.DifferencesString);

或者,如果您需要更改某些配置,请使用

CompareLogic basicComparison = new CompareLogic() 
{ Config = new ComparisonConfig()
   { MaxDifferences = propertyCount 
     //add other configurations
   }
};

可配置参数的完整列表在ComparisonConfig.cs中

原始答案:

我在您的代码中看到的限制:

  • 最大的一个是它不进行深层对象比较。

  • 在属性是列表或包含列表作为元素的情况下,它不会按元素进行比较(这可以是n级)。

  • 它没有考虑到不应比较某些类型的属性(例如,用于过滤目的的Func属性,例如PagedCollectionView类中的属性)。

  • 它不会跟踪实际上哪些属性是不同的(因此您可以在断言中显示)。

我今天正在寻找一些用于单元测试目的的解决方案,以便逐个属性进行深度比较,最终我使用了:http : //comparenetobjects.codeplex.com

这是一个只有一个类的免费库,您可以像这样简单地使用它:

var compareObjects = new CompareObjects()
{
    CompareChildren = true, //this turns deep compare one, otherwise it's shallow
    CompareFields = false,
    CompareReadOnly = true,
    ComparePrivateFields = false,
    ComparePrivateProperties = false,
    CompareProperties = true,
    MaxDifferences = 1,
    ElementsToIgnore = new List<string>() { "Filter" }
};

Assert.IsTrue(
    compareObjects.Compare(objectA, objectB), 
    compareObjects.DifferencesString
);

同样,可以轻松地将其重新编译为Silverlight。只需将一个类复制到Silverlight项目中,并删除一两行代码即可进行Silverlight中不可用的比较,例如私有成员比较。


2
Liviu,我注意到您对类与Silverlight不兼容的评论。我只是将其更改为与Silverlight和Windows Phone 7兼容。请获取最新信息。请参阅比较集74131,网址为comparenetobjects.codeplex.com/SourceControl/list/changesets
Greg

这看起来很有希望。要尝试一下
DJ Burb

谢谢你的榜样!另外,IgnoreObjectTypes当类型不同时,设置可能会很有用。
谢尔盖·布鲁诺夫

版本2.0具有可移植类库版本,该版本与Silverlight 5 +,Windows Phone 8 +,WinRT 8 +,Xamarin IOS和Xamarin Droid
兼容

DifferencesString已在CompareObjects类中弃用。但是现在您可以从var r = compareObjects.Compare(objectA, objectB); Assert.IsTrue(r.AreEqual, r.DifferencesString);
CompareResult中

6

我认为最好遵循Override Object#Equals()的模式以
获得更好的描述:阅读Bill Wagner的Effective C# -我认为第9项

public override Equals(object obOther)
{
  if (null == obOther)
    return false;
  if (object.ReferenceEquals(this, obOther)
    return true;
  if (this.GetType() != obOther.GetType())
    return false;
  # private method to compare members.
  return CompareMembers(this, obOther as ThisClass);
}
  • 同样,在检查相等性的方法中,您应该返回true或false。它们相等或不相等。而不是引发异常,而是返回false。
  • 我会考虑覆盖Object#Equals。
  • 即使您必须考虑到这一点,使用反射来比较属性也应该很慢(我没有数字可以支持)。这是C#中valueType#Equals的默认行为,建议您为值类型覆盖Equals并进行成员明智的性能比较。(之前我快速阅读了这篇文章,因为您有一组自定义的Property对象……我的错。)

2011年12月更新:

  • 当然,如果类型已经具有生产Equals(),则需要另一种方法。
  • 如果您仅使用此方法比较不可变的数据结构以用于测试目的,则不应将Equals添加到生产类中(某人可能通过链接Equals实现来简化测试,或者可能阻止创建生产所需的Equals实现) 。

我遇到了覆盖.Equals()的问题,因为我试图在继承的基类上实现它……因为我不知道将针对该类运行的类的键,所以我无法为GetHasCode()实现体面的重写(重写Equals()时需要)。
nailitdown '02

要求是,如果objA.Equals(objB),则objA.GetHashCode()== objB.GetHashCode()。GetHashCode不应依赖于类的可变状态/数据……我没有得到类键的含义。似乎可以解决。基本类型没有“键”吗?
Gishu

6

如果性能无关紧要,则可以序列化它们并比较结果:

var serializer = new XmlSerializer(typeof(TheObjectType));
StringWriter serialized1 = new StringWriter(), serialized2 = new StringWriter();
serializer.Serialize(serialized1, obj1);
serializer.Serialize(serialized2, obj2);
bool areEqual = serialized1.ToString() == serialized2.ToString();

4
尝试了一下,您会想知道有多少对象不可序列化...
Offler 2012年

5

我认为Big T的答案很好,但是缺少深入的比较,因此我做了一些调整:

using System.Collections.Generic;
using System.Reflection;

/// <summary>Comparison class.</summary>
public static class Compare
{
    /// <summary>Compare the public instance properties. Uses deep comparison.</summary>
    /// <param name="self">The reference object.</param>
    /// <param name="to">The object to compare.</param>
    /// <param name="ignore">Ignore property with name.</param>
    /// <typeparam name="T">Type of objects.</typeparam>
    /// <returns><see cref="bool">True</see> if both objects are equal, else <see cref="bool">false</see>.</returns>
    public static bool PublicInstancePropertiesEqual<T>(T self, T to, params string[] ignore) where T : class
    {
        if (self != null && to != null)
        {
            var type = self.GetType();
            var ignoreList = new List<string>(ignore);
            foreach (var pi in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
            {
                if (ignoreList.Contains(pi.Name))
                {
                    continue;
                }

                var selfValue = type.GetProperty(pi.Name).GetValue(self, null);
                var toValue = type.GetProperty(pi.Name).GetValue(to, null);

                if (pi.PropertyType.IsClass && !pi.PropertyType.Module.ScopeName.Equals("CommonLanguageRuntimeLibrary"))
                {
                    // Check of "CommonLanguageRuntimeLibrary" is needed because string is also a class
                    if (PublicInstancePropertiesEqual(selfValue, toValue, ignore))
                    {
                        continue;
                    }

                    return false;
                }

                if (selfValue != toValue && (selfValue == null || !selfValue.Equals(toValue)))
                {
                    return false;
                }
            }

            return true;
        }

        return self == to;
    }
}

4

我会将以下行添加到PublicInstancePropertiesEqual方法,以避免复制和粘贴错误:

Assert.AreNotSame(self, to);

2

是否在属性中的所有对象上覆盖.ToString()?否则,第二次比较可能会返回null。

另外,在第二次比较中,就可读性而言,从现在起六个月/两年,我对!(A == B)与(A!= B)的构造持怀疑态度。该行本身很宽,如果您有一个宽的显示器也可以,但是打印效果可能不太好。(nitpick)

是否所有对象都始终使用属性,以便此代码可以正常工作?是否会有一些内部的非专有数据可能从一个对象到另一个对象不同,但是所有公开的数据都相同?我正在考虑一些可能随时间变化的数据,例如两个随机数生成器碰巧在同一点命中相同的数字,但是将产生两个不同的信息序列,或者只是任何不会暴露的数据通过属性界面。


好点-!= ...同意,得分。ToString()试图解决.GetValue返回一个对象(因此比较始终为false,因为它是ref比较)。是否有更好的方法?
nailitdown

如果GetValue返回一个对象,您可以再次通过该函数递归吗?即,在返回的对象上调用PropertiesEqual?
mmr

1

如果您只比较相同类型的对象或继承链中的更进一步的对象,为什么不将参数指定为基本类型而不是对象呢?

还要对参数进行空检查。

此外,我会使用'var'只是为了使代码更具可读性(如果是c#3代码)

另外,如果对象具有引用类型作为属性,那么您只是在它们上调用ToString(),它实际上并不比较值。如果未重写ToString,则它将仅以字符串形式返回类型名称,这可能会返回假阳性。


关于引用类型的一个好点-在我看来,这并不重要,但是很有可能。
nailitdown

1

我建议的第一件事是拆分实际比较,以使其更具可读性(我还取出了ToString()-是否需要这样做?):

else {
    object originalProperty = sourceType.GetProperty(pi.Name).GetValue(this, null);
    object comparisonProperty = destinationType.GetProperty(pi.Name).GetValue(comparisonObject, null);

    if (originalProperty != comparisonProperty)
        return false;

下一个建议是尽可能减少反射的使用-这确实很慢。我的意思是,真的很慢。如果要执行此操作,建议您缓存属性引用。我对Reflection API并不十分熟悉,因此,如果有点不对劲,请进行调整使其可以编译:

// elsewhere
Dictionary<object, Property[]> lookupDictionary = new Dictionary<object, Property[]>;

Property[] objectProperties = null;
if (lookupDictionary.ContainsKey(sourceType)) {
  objectProperties = lookupProperties[sourceType];
} else {
  // build array of Property references
  PropertyInfo[] sourcePropertyInfos = sourceType.GetProperties();
  Property[] sourceProperties = new Property[sourcePropertyInfos.length];
  for (int i=0; i < sourcePropertyInfos.length; i++) {
    sourceProperties[i] = sourceType.GetProperty(pi.Name);
  }
  // add to cache
  objectProperties = sourceProperties;
  lookupDictionary[object] = sourceProperties;
}

// loop through and compare against the instances

但是,我不得不说我同意其他海报。这闻起来很懒和效率低下。您应该改为实现IComparable :-)。


我只是在看IComparable,但似乎是用于排序和排序的。对比较两个对象的相等性真的有用吗?
nailitdown

绝对地,因为.Equals(object o)定义为this.CompareTo(o)==0。因此,equals使用ComparesTo()来确定相等性。这将比使用反射更为有效(和标准做法)。
tsimon

假设参照CompareTo()实现了Equals(或应该实现),我可能会误解。您应按此处所述考虑覆盖等于:stackoverflow.com/questions/104158/…–
tsimon

1

这里修改为将null = null等同

 private bool PublicInstancePropertiesEqual<T>(T self, T to, params string[] ignore) where T : class
        {
            if (self != null && to != null)
            {
                Type type = typeof(T);
                List<string> ignoreList = new List<string>(ignore);
                foreach (PropertyInfo pi in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
                {
                    if (!ignoreList.Contains(pi.Name))
                    {
                        object selfValue = type.GetProperty(pi.Name).GetValue(self, null);
                        object toValue = type.GetProperty(pi.Name).GetValue(to, null);
                        if (selfValue != null)
                        {
                            if (!selfValue.Equals(toValue))
                                return false;
                        }
                        else if (toValue != null)
                            return false;
                    }
                }
                return true;
            }
            return self == to;
        }

如果我有一个深层的对象图,最好的方法是使用上面的方法来返回已更改的旧属性和新属性的列表?
罗德

1

我最终这样做:

    public static string ToStringNullSafe(this object obj)
    {
        return obj != null ? obj.ToString() : String.Empty;
    }
    public static bool Compare<T>(T a, T b)
    {
        int count = a.GetType().GetProperties().Count();
        string aa, bb;
        for (int i = 0; i < count; i++)
        {
            aa = a.GetType().GetProperties()[i].GetValue(a, null).ToStringNullSafe();
            bb = b.GetType().GetProperties()[i].GetValue(b, null).ToStringNullSafe();
            if (aa != bb)
            {
                return false;
            }
        }
        return true;
    }

用法:

    if (Compare<ObjectType>(a, b))

更新资料

如果要按名称忽略某些属性:

    public static string ToStringNullSafe(this object obj)
    {
        return obj != null ? obj.ToString() : String.Empty;
    }
    public static bool Compare<T>(T a, T b, params string[] ignore)
    {
        int count = a.GetType().GetProperties().Count();
        string aa, bb;
        for (int i = 0; i < count; i++)
        {
            aa = a.GetType().GetProperties()[i].GetValue(a, null).ToStringNullSafe();
            bb = b.GetType().GetProperties()[i].GetValue(b, null).ToStringNullSafe();
            if (aa != bb && ignore.Where(x => x == a.GetType().GetProperties()[i].Name).Count() == 0)
            {
                return false;
            }
        }
        return true;
    }

用法:

    if (MyFunction.Compare<ObjType>(a, b, "Id","AnotherProp"))

1

您可以通过每种类型仅调用一次GetProperties来优化代码:

public static string ToStringNullSafe(this object obj)
{
    return obj != null ? obj.ToString() : String.Empty;
}
public static bool Compare<T>(T a, T b, params string[] ignore)
{
    var aProps = a.GetType().GetProperties();
    var bProps = b.GetType().GetProperties();
    int count = aProps.Count();
    string aa, bb;
    for (int i = 0; i < count; i++)
    {
        aa = aProps[i].GetValue(a, null).ToStringNullSafe();
        bb = bProps[i].GetValue(b, null).ToStringNullSafe();
        if (aa != bb && ignore.Where(x => x == aProps[i].Name).Count() == 0)
        {
            return false;
        }
    }
    return true;
}


1

确保没有物体 null

obj1obj2

if(obj1 == null )
{
   return false;
}
return obj1.Equals( obj2 );

如果它们都为空怎么办?他们不是平等的吗?
mmr

空值的好处,在我的情况下,使用.Equals()似乎无效,这就是为什么我提出了这种解决方案的
原因–nailitdown

好吧,我要测试的情况是两个对象,一个是新创建的,一个是会话中的。即使两者都具有相同的属性值,将两者与.Equals()进行比较也会返回false
nailitdown

0

即使对象不同,此方法也有效。您可以自定义实用程序类中的方法,也许您也想比较私有属性...

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

class ObjectA
{
    public string PropertyA { get; set; }
    public string PropertyB { get; set; }
    public string PropertyC { get; set; }
    public DateTime PropertyD { get; set; }

    public string FieldA;
    public DateTime FieldB;
}

class ObjectB
{
    public string PropertyA { get; set; }
    public string PropertyB { get; set; }
    public string PropertyC { get; set; }
    public DateTime PropertyD { get; set; }


    public string FieldA;
    public DateTime FieldB;


}

class Program
{
    static void Main(string[] args)
    {
        // create two objects with same properties
        ObjectA a = new ObjectA() { PropertyA = "test", PropertyB = "test2", PropertyC = "test3" };
        ObjectB b = new ObjectB() { PropertyA = "test", PropertyB = "test2", PropertyC = "test3" };

        // add fields to those objects
        a.FieldA = "hello";
        b.FieldA = "Something differnt";

        if (a.ComparePropertiesTo(b))
        {
            Console.WriteLine("objects have the same properties");
        }
        else
        {
            Console.WriteLine("objects have diferent properties!");
        }


        if (a.CompareFieldsTo(b))
        {
            Console.WriteLine("objects have the same Fields");
        }
        else
        {
            Console.WriteLine("objects have diferent Fields!");
        }

        Console.Read();
    }
}

public static class Utilities
{
    public static bool ComparePropertiesTo(this Object a, Object b)
    {
        System.Reflection.PropertyInfo[] properties = a.GetType().GetProperties(); // get all the properties of object a

        foreach (var property in properties)
        {
            var propertyName = property.Name;

            var aValue = a.GetType().GetProperty(propertyName).GetValue(a, null);
            object bValue;

            try // try to get the same property from object b. maybe that property does
                // not exist! 
            {
                bValue = b.GetType().GetProperty(propertyName).GetValue(b, null);
            }
            catch
            {
                return false;
            }

            if (aValue == null && bValue == null)
                continue;

            if (aValue == null && bValue != null)
                return false;

            if (aValue != null && bValue == null)
               return false;

            // if properties do not match return false
            if (aValue.GetHashCode() != bValue.GetHashCode())
            {
                return false;
            }
        }

        return true;
    }



    public static bool CompareFieldsTo(this Object a, Object b)
    {
        System.Reflection.FieldInfo[] fields = a.GetType().GetFields(); // get all the properties of object a

        foreach (var field in fields)
        {
            var fieldName = field.Name;

            var aValue = a.GetType().GetField(fieldName).GetValue(a);

            object bValue;

            try // try to get the same property from object b. maybe that property does
            // not exist! 
            {
                bValue = b.GetType().GetField(fieldName).GetValue(b);
            }
            catch
            {
                return false;
            }

            if (aValue == null && bValue == null)
               continue;

            if (aValue == null && bValue != null)
               return false;

            if (aValue != null && bValue == null)
               return false;


            // if properties do not match return false
            if (aValue.GetHashCode() != bValue.GetHashCode())
            {
                return false;
            }
        }

        return true;
    }


}

该代码不是100%有效的。它在某些情况下不起作用,例如,如果包含类型为object的属性。
Tono Nam

0

Liviu的答案的最新更新-CompareObjects.DifferencesString已被弃用。

这在单元测试中效果很好:

CompareLogic compareLogic = new CompareLogic();
ComparisonResult result = compareLogic.Compare(object1, object2);
Assert.IsTrue(result.AreEqual);

1
您修复了贬损的现象真是太好了,但是我认为这个答案实际上应该是Liviu答案中的一条评论。特别是因为您的示例代码(与Liviu的代码相比)缺少CompareLogic的参数(我确定这很重要),还缺少assert消息(不推荐使用的参数)。该断言可以通过以下方式解决:Assert.IsTrue(result.AreEqual, result.DifferencesString);
Mariano Desanze 2014年

0

此方法将获得properties类并比较每个的值property。如果任何一个值不同,它将不同return false,否则return true

public static bool Compare<T>(T Object1, T object2)
{
    //Get the type of the object
    Type type = typeof(T);

    //return false if any of the object is false
    if (Object1 == null || object2 == null)
        return false;

    //Loop through each properties inside class and get values for the property from both the objects and compare
    foreach (System.Reflection.PropertyInfo property in type.GetProperties())
    {
        if (property.Name != "ExtensionData")
        {
            string Object1Value = string.Empty;
            string Object2Value = string.Empty;
            if (type.GetProperty(property.Name).GetValue(Object1, null) != null)
                Object1Value = type.GetProperty(property.Name).GetValue(Object1, null).ToString();
            if (type.GetProperty(property.Name).GetValue(object2, null) != null)
                Object2Value = type.GetProperty(property.Name).GetValue(object2, null).ToString();
            if (Object1Value.Trim() != Object2Value.Trim())
            {
                return false;
            }
        }
    }
    return true;
}

用法:

bool isEqual = Compare<Employee>(Object1, Object2)


0

为了扩展@nawfal:s的答案,我使用它来测试单元测试中不同类型的对象,以比较相等的属性名称。就我而言,数据库实体和DTO。

在我的测试中像这样使用;

Assert.IsTrue(resultDto.PublicInstancePropertiesEqual(expectedEntity));



public static bool PublicInstancePropertiesEqual<T, Z>(this T self, Z to, params string[] ignore) where T : class
{
    if (self != null && to != null)
    {
        var type = typeof(T);
        var type2 = typeof(Z);
        var ignoreList = new List<string>(ignore);
        var unequalProperties =
           from pi in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
           where !ignoreList.Contains(pi.Name)
           let selfValue = type.GetProperty(pi.Name).GetValue(self, null)
           let toValue = type2.GetProperty(pi.Name).GetValue(to, null)
           where selfValue != toValue && (selfValue == null || !selfValue.Equals(toValue))
           select selfValue;
           return !unequalProperties.Any();
    }
    return self == null && to == null;
}

0

有时您不想比较所有公共属性,而只想比较它们的子集,因此在这种情况下,您可以移动逻辑以将所需的属性列表与抽象类进行比较

public abstract class ValueObject<T> where T : ValueObject<T>
{
    protected abstract IEnumerable<object> GetAttributesToIncludeInEqualityCheck();

    public override bool Equals(object other)
    {
        return Equals(other as T);
    }

    public bool Equals(T other)
    {
        if (other == null)
        {
            return false;
        }

        return GetAttributesToIncludeInEqualityCheck()
            .SequenceEqual(other.GetAttributesToIncludeInEqualityCheck());
    }

    public static bool operator ==(ValueObject<T> left, ValueObject<T> right)
    {
        return Equals(left, right);
    }

    public static bool operator !=(ValueObject<T> left, ValueObject<T> right)
    {
        return !(left == right);
    }

    public override int GetHashCode()
    {
        int hash = 17;
        foreach (var obj in this.GetAttributesToIncludeInEqualityCheck())
            hash = hash * 31 + (obj == null ? 0 : obj.GetHashCode());

        return hash;
    }
}

并在以后使用此抽象类比较对象

public class Meters : ValueObject<Meters>
{
    ...

    protected decimal DistanceInMeters { get; private set; }

    ...

    protected override IEnumerable<object> GetAttributesToIncludeInEqualityCheck()
    {
        return new List<Object> { DistanceInMeters };
    }
}

0

我的解决方案受到以上Aras Alenin答案的启发,我在其中添加了一个级别的对象比较和一个自定义对象以用于比较结果。我也想获得带有对象名称的属性名称:

    public static IEnumerable<ObjectPropertyChanged> GetPublicSimplePropertiesChanged<T>(this T previous, T proposedChange,
     string[] namesOfPropertiesToBeIgnored) where T : class
    {
        return GetPublicGenericPropertiesChanged(previous, proposedChange, namesOfPropertiesToBeIgnored, true, null, null);
    }

    public static IReadOnlyList<ObjectPropertyChanged> GetPublicGenericPropertiesChanged<T>(this T previous, T proposedChange,
        string[] namesOfPropertiesToBeIgnored) where T : class
    {
        return GetPublicGenericPropertiesChanged(previous, proposedChange, namesOfPropertiesToBeIgnored, false, null, null);
    }

    /// <summary>
    /// Gets the names of the public properties which values differs between first and second objects.
    /// Considers 'simple' properties AND for complex properties without index, get the simple properties of the children objects.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="previous">The previous object.</param>
    /// <param name="proposedChange">The second object which should be the new one.</param>
    /// <param name="namesOfPropertiesToBeIgnored">The names of the properties to be ignored.</param>
    /// <param name="simpleTypeOnly">if set to <c>true</c> consider simple types only.</param>
    /// <param name="parentTypeString">The parent type string. Meant only for recursive call with simpleTypeOnly set to <c>true</c>.</param>
    /// <param name="secondType">when calling recursively, the current type of T must be clearly defined here, as T will be more generic (using base class).</param>
    /// <returns>
    /// the names of the properties
    /// </returns>
    private static IReadOnlyList<ObjectPropertyChanged> GetPublicGenericPropertiesChanged<T>(this T previous, T proposedChange,
        string[] namesOfPropertiesToBeIgnored, bool simpleTypeOnly, string parentTypeString, Type secondType) where T : class
    {
        List<ObjectPropertyChanged> propertiesChanged = new List<ObjectPropertyChanged>();

        if (previous != null && proposedChange != null)
        {
            var type = secondType == null ? typeof(T) : secondType;
            string typeStr = parentTypeString + type.Name + ".";
            var ignoreList = namesOfPropertiesToBeIgnored.CreateList();
            IEnumerable<IEnumerable<ObjectPropertyChanged>> genericPropertiesChanged =
                from pi in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
                where !ignoreList.Contains(pi.Name) && pi.GetIndexParameters().Length == 0 
                    && (!simpleTypeOnly || simpleTypeOnly && pi.PropertyType.IsSimpleType())
                let firstValue = type.GetProperty(pi.Name).GetValue(previous, null)
                let secondValue = type.GetProperty(pi.Name).GetValue(proposedChange, null)
                where firstValue != secondValue && (firstValue == null || !firstValue.Equals(secondValue))
                let subPropertiesChanged = simpleTypeOnly || pi.PropertyType.IsSimpleType()
                    ? null
                    : GetPublicGenericPropertiesChanged(firstValue, secondValue, namesOfPropertiesToBeIgnored, true, typeStr, pi.PropertyType)
                let objectPropertiesChanged = subPropertiesChanged != null && subPropertiesChanged.Count() > 0
                    ? subPropertiesChanged
                    : (new ObjectPropertyChanged(proposedChange.ToString(), typeStr + pi.Name, firstValue.ToStringOrNull(), secondValue.ToStringOrNull())).CreateList()
                select objectPropertiesChanged;

            if (genericPropertiesChanged != null)
            {   // get items from sub lists
                genericPropertiesChanged.ForEach(a => propertiesChanged.AddRange(a));
            }
        }
        return propertiesChanged;
    }

使用以下类存储比较结果

[System.Serializable]
public class ObjectPropertyChanged
{
    public ObjectPropertyChanged(string objectId, string propertyName, string previousValue, string changedValue)
    {
        ObjectId = objectId;
        PropertyName = propertyName;
        PreviousValue = previousValue;
        ProposedChangedValue = changedValue;
    }

    public string ObjectId { get; set; }

    public string PropertyName { get; set; }

    public string PreviousValue { get; set; }

    public string ProposedChangedValue { get; set; }
}

和样本单元测试:

    [TestMethod()]
    public void GetPublicGenericPropertiesChangedTest1()
    {
        // Define objects to test
        Function func1 = new Function { Id = 1, Description = "func1" };
        Function func2 = new Function { Id = 2, Description = "func2" };
        FunctionAssignment funcAss1 = new FunctionAssignment
        {
            Function = func1,
            Level = 1
        };
        FunctionAssignment funcAss2 = new FunctionAssignment
        {
            Function = func2,
            Level = 2
        };

        // Main test: read properties changed
        var propertiesChanged = Utils.GetPublicGenericPropertiesChanged(funcAss1, funcAss2, null);

        Assert.IsNotNull(propertiesChanged);
        Assert.IsTrue(propertiesChanged.Count == 3);
        Assert.IsTrue(propertiesChanged[0].PropertyName == "FunctionAssignment.Function.Description");
        Assert.IsTrue(propertiesChanged[1].PropertyName == "FunctionAssignment.Function.Id");
        Assert.IsTrue(propertiesChanged[2].PropertyName == "FunctionAssignment.Level");
    }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.