当前位置:  开发笔记 > 编程语言 > 正文

比较c#中的对象属性

如何解决《比较c#中的对象属性》经验,为你挑选了5个好方法。

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

现在,这确实有效 - 但为了提高我的代码质量,我想我会把它扔出去仔细检查.它怎么能更好/更有效/等等?

/// 
/// Compare property values (as strings)
/// 
/// 
/// 
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;
}

Taras Alenin.. 160

我正在寻找一些代码片段,它们可以做类似于编写单元测试的帮助.这是我最终使用的.

public static bool PublicInstancePropertiesEqual(T self, T to, params string[] ignore) where T : class 
  {
     if (self != null && to != null)
     {
        Type type = typeof(T);
        List ignoreList = new List(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(this T self, T to, params string[] ignore) where T : class
{
    if (self != null && to != null)
    {
        var type = typeof(T);
        var ignoreList = new List(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
   {
      /// 
      /// Determine whether a type is simple (String, Decimal, DateTime, etc) 
      /// or complex (i.e. custom class with public properties and methods).
      /// 
      /// 
      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"
               );
         }
      }
   }


Liviu Trifoi.. 64

更新: 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);

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

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

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

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



1> Taras Alenin..:

我正在寻找一些代码片段,它们可以做类似于编写单元测试的帮助.这是我最终使用的.

public static bool PublicInstancePropertiesEqual(T self, T to, params string[] ignore) where T : class 
  {
     if (self != null && to != null)
     {
        Type type = typeof(T);
        List ignoreList = new List(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(this T self, T to, params string[] ignore) where T : class
{
    if (self != null && to != null)
    {
        var type = typeof(T);
        var ignoreList = new List(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
   {
      /// 
      /// Determine whether a type is simple (String, Decimal, DateTime, etc) 
      /// or complex (i.e. custom class with public properties and methods).
      /// 
      /// 
      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"
               );
         }
      }
   }



2> Liviu Trifoi..:

更新: 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);

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

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

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

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


Liviu,我注意到你对该课程与Silverlight不兼容的评论.我只是将其更改为与Silverlight和Windows Phone 7兼容.做最新的.请参阅http://comparenetobjects.codeplex.com/SourceControl/list/changesets上的更改集74131

3> Gishu..:

我认为最好遵循Override Object#Equals()的模式以
获得更好的描述:阅读Bill Wagner的有效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,并对性能进行成员比较.(之前我速读这个,因为你有自定义属性对象的集合......我的坏.)

2011年12月更新:

当然,如果类型已经有生产Equals(),那么你需要另一种方法.

如果您使用它来仅仅为了测试目的来比较不可变数据结构,那么您不应该将Equals添加到生产类(有人可能通过使用Equals实现来加密测试,或者您可能会阻止创建生产所需的Equals实现) .



4> Edward Brey..:

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

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


尝试过这个问题,您会想知道有多少对象不可序列化......

5> 小智..:

我认为Big T的答案相当不错,但缺乏深度比较,所以我稍微调整了一下:

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

/// Comparison class.
public static class Compare
{
    /// Compare the public instance properties. Uses deep comparison.
    /// The reference object.
    /// The object to compare.
    /// Ignore property with name.
    /// Type of objects.
    /// True if both objects are equal, else false.
    public static bool PublicInstancePropertiesEqual(T self, T to, params string[] ignore) where T : class
    {
        if (self != null && to != null)
        {
            var type = self.GetType();
            var ignoreList = new List(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;
    }
}

推荐阅读
牛尾巴2010
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有