比较c#中的对象属性

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/506096/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 06:03:58  来源:igfitidea点击:

Comparing object properties in c#

c#objectpropertiescomparison

提问by nailitdown

This is what I've come up with as a method on a class inherited by many of my other classes. The idea is that it allows the simple comparison between properties of Objects of the same Type.

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

Now, this does work - but in the interest of improving the quality of my code I thought I'd throw it out for scrutiny. How can it be better/more efficient/etc.?

现在,这确实有效 - 但为了提高我的代码质量,我想我会把它扔掉进行审查。它如何更好/更有效/等等?

/// <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;
}

采纳答案by Taras Alenin

I was looking for a snippet of code that would do something similar to help with writing unit test. Here is what I ended up using.

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

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;
  }

EDIT:

编辑:

Same code as above but uses LINQ and Extension methods :

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

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"
               );
         }
      }
   }

回答by Ric Tokyo

Make sure objects aren't null.

确保对象不是null.

Having obj1and obj2:

拥有obj1obj2

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

回答by mmr

Do you override .ToString() on all of your objects that are in the properties? Otherwise, that second comparison could come back with null.

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

Also, in that second comparison, I'm on the fence about the construct of !( A == B) compared to (A != B), in terms of readability six months/two years from now. The line itself is pretty wide, which is ok if you've got a wide monitor, but might not print out very well. (nitpick)

另外,在第二次比较中,就六个月/两年后的可读性而言,我对 !( A == B) 与 (A != B) 的结构持怀疑态度。这条线本身很宽,如果你有一个宽显示器,这是可以的,但可能不会很好地打印出来。(挑剔)

Are all of your objects always using properties such that this code will work? Could there be some internal, non-propertied data that could be different from one object to another, but all exposed data is the same? I'm thinking of some data which could change over time, like two random number generators that happen to hit the same number at one point, but are going to produce two different sequences of information, or just any data that doesn't get exposed through the property interface.

您的所有对象是否总是使用属性以便此代码可以工作?是否有一些内部的、非属性的数据在一个对象与另一个对象之间可能不同,但所有公开的数据都是相同的?我在考虑一些可能会随时间变化的数据,比如两个随机数生成器,它们碰巧在某一时刻命中相同的数字,但会产生两种不同的信息序列,或者只是任何不会暴露的数据通过属性接口。

回答by DarkwingDuck

If you are only comparing objects of the same type or further down the inheritance chain, why not specify the parameter as your base type, rather than object ?

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

Also do null checks on the parameter as well.

还要对参数进行空检查。

Furthermore I'd make use of 'var' just to make the code more readable (if its c#3 code)

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

Also, if the object has reference types as properties then you are just calling ToString() on them which doesn't really compare values. If ToString isn't overwridden then its just going to return the type name as a string which could return false-positives.

此外,如果对象具有作为属性的引用类型,那么您只是在它们上调用 ToString() 而不会真正比较值。如果 ToString 没有被覆盖,那么它只会将类型名称作为可能返回误报的字符串返回。

回答by Gishu

I think it would be best to follow the pattern for Override Object#Equals()
For a better description: Read Bill Wagner's Effective C#- Item 9 I think

我认为最好遵循 Override Object#Equals() 的模式以
获得更好的描述:阅读 Bill Wagner 的Effective C#- Item 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);
}
  • Also in methods that check for equality, you should return either true or false. either they are equal or they are not.. instead of throwing an exception, return false.
  • I'd consider overriding Object#Equals.
  • Even though you must have considered this, using Reflection to compare properties is supposedly slow (I dont have numbers to back this up). This is the default behavior for valueType#Equals in C# and it is recommended that you override Equals for value types and do a member wise compare for performance. (Earlier I speed-read this as you have a collection of custom Property objects... my bad.)
  • 同样在检查相等性的方法中,您应该返回 true 或 false。它们要么相等,要么不相等……而不是抛出异常,返回 false。
  • 我会考虑覆盖 Object#Equals。
  • 尽管您必须考虑到这一点,但使用反射来比较属性据说很慢(我没有数字来支持这一点)。这是 C# 中 valueType#Equals 的默认行为,建议您覆盖值类型的 Equals 并执行成员明智的比较以提高性能。(早些时候我快速阅读了这篇文章,因为你有一组自定义的 Property 对象......我的错。)

Update-Dec 2011:

2011 年 12 月更新:

  • Of course, if the type already has a production Equals() then you need another approach.
  • If you're using this to compare immutable data structures exclusively for test purposes, you shouldn't add an Equals to production classes (Someone might hose the tests by chainging the Equals implementation or you may prevent creation of a production-required Equals implementation).
  • 当然,如果类型已经有一个生产 Equals() 那么你需要另一种方法。
  • 如果您使用它来比较专门用于测试目的的不可变数据结构,则不应将 Equals 添加到生产类(有人可能会通过链接 Equals 实现来管理测试,或者您可能会阻止创建生产所需的 Equals 实现) .

回答by tsimon

The first thing I would suggest would be to split up the actual comparison so that it's a bit more readable (I've also taken out the ToString() - is that needed?):

我建议的第一件事是拆分实际比较,以便它更具可读性(我还取出了 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;

The next suggestion would be to minimise the use of reflection as much as possible - it's really slow. I mean, reallyslow. If you are going to do this, I would suggest caching the property references. I'm not intimately familiar with the Reflection API, so if this is a bit off, just adjust to make it compile:

下一个建议是尽可能减少反射的使用——这真的很慢。我的意思是,真的很慢。如果您打算这样做,我建议缓存属性引用。我对 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

However, I have to say that I agree with the other posters. This smells lazy and inefficient. You should be implementing IComparable instead :-).

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

回答by Hossein

here is revised one to treat null = null as equal

这里修改了一个以将 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;
        }

回答by Liviu Trifoi

UPDATE:The latest version of Compare-Net-Objects is located on GitHub, has NuGet packageand Tutorial. It can be called like

更新:最新版本的 Compare-Net-Objects 位于GitHub 上,有NuGet 包教程。它可以被称为

//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);

Or if you need to change some configuration, use

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

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

Full list of configurable parameters is in ComparisonConfig.cs

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

Original answer:

原答案:

The limitations I see in your code:

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

  • The biggest one is that it doesn't do a deep object comparison.

  • It doesn't do an element by element comparison in case properties are lists or contain lists as elements (this can go n-levels).

  • It doesn't take into account that some type of properties should not be compared (e.g. a Func property used for filtering purposes, like the one in the PagedCollectionView class).

  • It doesn't keep track of what properties actually were different (so you can show in your assertions).

  • 最大的一个是它没有做深度对象比较。

  • 如果属性是列表或包含列表作为元素(这可以是 n 级),它不会逐个元素进行比较。

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

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

I was looking today for some solution for unit-testing purposes to do property by property deep comparison and I ended up using: http://comparenetobjects.codeplex.com.

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

It is a free library with just one class which you can simply use like this:

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

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
);

Also, it can be easily re-compiled for Silverlight. Just copy the one class into a Silverlight project and remove one or two lines of code for comparisons that are not available in Silverlight, like private members comparison.

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

回答by thanei

I would add the following line to the PublicInstancePropertiesEqual method to avoid copy & paste errors:

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

Assert.AreNotSame(self, to);

回答by Tono Nam

This works even if the objects are different. you could customize the methods in the utilities class maybe you want to compare private properties as well...

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

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;
    }


}