두 개의 복잡한 개체를 비교하는 가장 좋은 방법


112

내가 좋아하는이 개 복잡한 객체를 가지고 Object1Object2. 약 5 단계의 하위 개체가 있습니다.

같은지 아닌지 말할 수있는 가장 빠른 방법이 필요합니다.

C # 4.0에서 어떻게이 작업을 수행 할 수 있습니까?

답변:


101

모든 사용자 지정 형식에 대해 구현 IEquatable<T>(일반적으로 상속 된 메서드 Object.EqualsObject.GetHashCode메서드 재정의와 함께 )합니다. 복합 유형의 경우 포함 유형 Equals내에서 포함 된 유형의 메소드를 호출하십시오 . 포함 된 컬렉션의 경우 SequenceEqual내부적으로 IEquatable<T>.Equals또는 Object.Equals각 요소를 호출 하는 확장 메서드를 사용합니다 . 이 접근 방식은 분명히 유형의 정의를 확장해야하지만 그 결과는 직렬화와 관련된 일반 솔루션보다 빠릅니다.

편집 : 다음은 세 가지 수준의 중첩이있는 인위적인 예입니다.

값 유형의 경우 일반적으로 해당 Equals메서드를 호출 할 수 있습니다 . 필드 또는 속성이 명시 적으로 할당되지 않은 경우에도 여전히 기본값이 있습니다.

참조 유형의 경우 먼저를 호출 ReferenceEquals하여 참조 동등성을 확인 해야 합니다. 이는 동일한 객체를 참조 할 때 효율성을 향상시키는 역할을합니다. 두 참조가 모두 null 인 경우도 처리합니다. 확인이 실패하면 인스턴스의 필드 또는 속성이 null이 아닌지 확인하고 (를 방지하기 위해 NullReferenceException) 해당 Equals메서드를 호출합니다 . 멤버가 적절하게 형식화 되었기 때문에 IEquatable<T>.Equals메서드는 재정의 된 Object.Equals메서드 (형식 캐스트로 인해 실행 속도가 약간 느려짐)를 우회하여 직접 호출됩니다 .

재정의 할 때 Object.Equals또한 재정의해야합니다 Object.GetHashCode. 간결함을 위해 아래에서는 그렇게하지 않았습니다.

public class Person : IEquatable<Person>
{
    public int Age { get; set; }
    public string FirstName { get; set; }
    public Address Address { get; set; }

    public override bool Equals(object obj)
    {
        return this.Equals(obj as Person);
    }

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

        return this.Age.Equals(other.Age) &&
            (
                object.ReferenceEquals(this.FirstName, other.FirstName) ||
                this.FirstName != null &&
                this.FirstName.Equals(other.FirstName)
            ) &&
            (
                object.ReferenceEquals(this.Address, other.Address) ||
                this.Address != null &&
                this.Address.Equals(other.Address)
            );
    }
}

public class Address : IEquatable<Address>
{
    public int HouseNo { get; set; }
    public string Street { get; set; }
    public City City { get; set; }

    public override bool Equals(object obj)
    {
        return this.Equals(obj as Address);
    }

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

        return this.HouseNo.Equals(other.HouseNo) &&
            (
                object.ReferenceEquals(this.Street, other.Street) ||
                this.Street != null &&
                this.Street.Equals(other.Street)
            ) &&
            (
                object.ReferenceEquals(this.City, other.City) ||
                this.City != null &&
                this.City.Equals(other.City)
            );
    }
}

public class City : IEquatable<City>
{
    public string Name { get; set; }

    public override bool Equals(object obj)
    {
        return this.Equals(obj as City);
    }

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

        return
            object.ReferenceEquals(this.Name, other.Name) ||
            this.Name != null &&
            this.Name.Equals(other.Name);
    }
}

업데이트 :이 답변은 몇 년 전에 작성되었습니다. 그 이후로 저는 IEquality<T>그러한 시나리오에 대해 변경 가능한 유형을 구현 하는 것에서 벗어나기 시작했습니다 . 평등에는 두 가지 개념이 있습니다. 정체성동등성 . 메모리 표현 수준에서 이들은 일반적으로 "참조 같음"과 "값 같음"으로 구분됩니다 (같음 비교 참조 ). 그러나 동일한 구분이 도메인 수준에서도 적용될 수 있습니다. Person클래스 PersonId에 고유 한 실제 사람마다 고유 한 속성 이 있다고 가정합니다 . PersonId같지만 다른 Age값을 가진 두 개체를 동일하거나 다른 것으로 간주 해야합니까 ? 위의 대답은 하나가 동등성 뒤에 있다고 가정합니다. 그러나 많은 용도가 있습니다.IEquality<T>이러한 구현이 identity를 제공한다고 가정하는 컬렉션과 같은 인터페이스 . 예를 들어를 채우는 경우 HashSet<T>일반적으로 TryGetValue(T,T)호출에서 인수의 ID 만 공유하는 기존 요소를 반환 할 것으로 예상 할 수 있으며 , 콘텐츠가 완전히 동일한 요소가 아닐 수도 있습니다. 이 개념은 다음에 대한 메모에 의해 시행됩니다 GetHashCode.

일반적으로 변경 가능한 참조 유형의 GetHashCode()경우 다음과 같은 경우에만 재정의해야 합니다.

  • 변경할 수없는 필드에서 해시 코드를 계산할 수 있습니다. 또는
  • 개체가 해시 코드에 의존하는 컬렉션에 포함되어있는 동안 변경 가능한 개체의 해시 코드가 변경되지 않도록 할 수 있습니다.

RIA 서비스를 통해이 개체를 가져옵니다 ... 해당 개체에 대해 IEquatable <Foo>를 사용하고 WPF 클라이언트에서 가져올 수 있습니까?
개발자

1
수업이 자동 생성된다는 뜻입니까? RIA 서비스를 사용하지 않았지만 생성 된 클래스는 다음과 같이 선언 될 것이라고 가정합니다 partial.이 경우 Equals자동 생성 된 필드 / 속성을 참조하는 수동으로 추가 된 부분 클래스 선언을 통해 해당 메서드를 구현할 수 있습니다. 하나.
Douglas

"주소 주소"가 실제로 "주소 [] 주소"이면 어떻게 구현됩니까?
guiomie

2
Enumerable.SequenceEqual배열 에서 LINQ 메서드를 호출 할 수 this.Addresses.SequenceEqual(other.Addresses)있습니다.. 이것은 클래스가 인터페이스를 구현 Address.Equals하는 경우 해당 주소의 각 쌍에 대해 내부적으로 메서드를 호출합니다 . AddressIEquatable<Address>
Douglas

2
개발자가 확인할 수있는 또 다른 비교 범주는 "WorksLike"입니다. 나에게 이것은 두 인스턴스가 같지 않은 속성 값을 가질 수 있지만 프로그램은 두 인스턴스를 처리하여 동일한 결과를 생성한다는 것을 의미합니다.
John Kurtz

95

두 개체를 직렬화하고 결과 문자열 비교


1
왜 그런지 모르겠습니다. 직렬화는 일반적으로 최적화 된 프로세스이며 어떤 경우에도 모든 속성의 값에 액세스해야합니다.
JoelFan

5
엄청난 비용이 듭니다. 데이터 스트림을 생성하고 문자열을 추가 한 다음 문자열 동등성을 테스트합니다. 그 정도면 그 자체입니다. 직렬화는 기본적으로 리플렉션을 사용할 것입니다.
Jerome Haltom

2
데이터 스트림은 큰 문제가 아닙니다. 문자열을 추가해야하는 이유를 모르겠습니다. 문자열 동등성 테스트는 가장 최적화 된 작업 중 하나입니다. 전체 직렬화는 다른 방법보다 "크기 순서"가 나쁘지 않습니다. 성능 문제가 의심되는 경우 벤치 마크를 수행해야합니다 ...이 방법으로 성능 문제를 경험 한 적이 없습니다
JoelFan

12
나는 +1값에 기반한 평등 비교를 이런 식으로 생각한 적이 없기 때문에 이것이다. 멋지고 간단합니다. 이 코드로 몇 가지 벤치 마크를 보는 것이 깔끔 할 것입니다.
Thomas

1
두 직렬화가 비슷한 방식으로 잘못 될 수 있으므로 이는 좋은 솔루션이 아닙니다. 예를 들어 소스 개체의 일부 속성이 직렬화되지 않았을 수 있으며 역 직렬화되면 대상 개체에서 null로 설정됩니다. 이 경우 문자열을 비교하는 테스트는 통과되지만 두 개체는 실제로 동일하지 않습니다!
stackMeUp

35

확장 방법, 재귀를 사용하여이 문제를 해결할 수 있습니다.

public static bool DeepCompare(this object obj, object another)
{     
  if (ReferenceEquals(obj, another)) return true;
  if ((obj == null) || (another == null)) return false;
  //Compare two object's class, return false if they are difference
  if (obj.GetType() != another.GetType()) return false;

  var result = true;
  //Get all properties of obj
  //And compare each other
  foreach (var property in obj.GetType().GetProperties())
  {
      var objValue = property.GetValue(obj);
      var anotherValue = property.GetValue(another);
      if (!objValue.Equals(anotherValue)) result = false;
  }

  return result;
 }

public static bool CompareEx(this object obj, object another)
{
 if (ReferenceEquals(obj, another)) return true;
 if ((obj == null) || (another == null)) return false;
 if (obj.GetType() != another.GetType()) return false;

 //properties: int, double, DateTime, etc, not class
 if (!obj.GetType().IsClass) return obj.Equals(another);

 var result = true;
 foreach (var property in obj.GetType().GetProperties())
 {
    var objValue = property.GetValue(obj);
    var anotherValue = property.GetValue(another);
    //Recursion
    if (!objValue.DeepCompare(anotherValue))   result = false;
 }
 return result;
}

또는 Json을 사용하여 비교 (객체가 매우 복잡한 경우) Newtonsoft.Json을 사용할 수 있습니다.

public static bool JsonCompare(this object obj, object another)
{
  if (ReferenceEquals(obj, another)) return true;
  if ((obj == null) || (another == null)) return false;
  if (obj.GetType() != another.GetType()) return false;

  var objJson = JsonConvert.SerializeObject(obj);
  var anotherJson = JsonConvert.SerializeObject(another);

  return objJson == anotherJson;
}

1
첫 번째 해결책은 훌륭합니다! json을 직렬화하거나 구현할 필요가 없어 객체 자체에 코드를 추가 할 필요가 없습니다. 단위 테스트를 위해 비교할 때 적합합니다. objValue와 anotherValue가 모두 null 인 경우 간단한 비교를 추가하는 것이 좋습니다. 이렇게하면 null을 시도 할 때 NullReferenceException이 발생하는 것을 방지 할 수 있습니다. null.Equals () // ReSharper는 한 번만 비활성화됩니다. RedundantJumpStatement if (objValue == anotherValue) continue; // null 참조 가드 else if (! objValue.Equals (anotherValue)) Fail (expected, actual);
Mark Conway

3
DeepCompare단순히 CompareEx재귀 적으로 호출 하는 대신 사용할 이유가 있습니까?
apostolov

3
이것은 불필요하게 전체 구조를 비교할 수 있습니다. 교체 result와 함께하면 return false더 효율적으로 만들 것입니다.
Tim Sylvester

24

IEquatable을 구현하지 않으려면 항상 Reflection을 사용하여 모든 속성을 비교할 수 있습니다.-값 유형이면 비교하기 만하면됩니다.-참조 유형 인 경우 함수를 재귀 적으로 호출하여 "내부"속성을 비교합니다. .

나는 성능에 대해 생각하지 않고 단순성에 대해 생각합니다. 그러나 개체의 정확한 디자인에 따라 다릅니다. 객체 모양에 따라 복잡해질 수 있습니다 (예 : 속성간에 순환 종속성이있는 경우). 그러나 다음과 같이 사용할 수있는 몇 가지 솔루션이 있습니다.

또 다른 옵션은 객체를 텍스트로 직렬화하는 것입니다. 예를 들어 JSON.NET을 사용하고 직렬화 결과를 비교합니다. (JSON.NET은 속성 간의 순환 종속성을 처리 할 수 ​​있습니다.)

가장 빠른 방법이이를 구현하는 가장 빠른 방법인지 또는 빠르게 실행되는 코드인지는 모르겠습니다. 필요한지 알기 전에 최적화해서는 안됩니다. 조기 최적화는 모든 악의 근원입니다


1
IEquatable<T>구현이 조기 최적화의 경우에 해당한다고 생각하지 않습니다 . 반사가 현저히 느려집니다. Equals사용자 정의 값 유형 에 대한 의 기본 구현은 리플렉션을 사용합니다. Microsoft는 성능을 위해이를 재정의 할 것을 권장합니다. " Equals특정 유형에 대한 메서드를 재정 의하여 메서드의 성능을 향상시키고 형식에 대한 동일성 개념을보다 밀접하게 나타냅니다."
Douglas

1
그는 equals 메소드를 몇 번 실행할 것인지에 따라 다릅니다. 1, 10, 100, 100, 백만? 그것은 큰 차이를 만들 것입니다. 아무것도 구현하지 않고 일반적인 솔루션을 사용할 수 있다면 귀중한 시간을 절약 할 수 있습니다. 너무 느리면 IEquatable을 구현할 때입니다. 즉, PropertyInfos 유형 재사용 여부).
JotaBe 2012 년

@ Worthy7 그렇습니다. 프로젝트의 내용을 봐주세요. 테스트는 예제로 문서화하는 좋은 방법입니다. 그러나 그보다 더 나은 방법은 찾아 보면 .chm 도움말 파일을 찾을 수 있다는 것입니다. 그래서이 프로젝트는 대부분의 프로젝트보다 훨씬 더 나은 문서를 가지고 있습니다.
JotaBe

죄송합니다. "위키"탭을 완전히 놓쳤습니다. 나는 readme에 글을 쓰는 모든 사람에게 익숙합니다.
Worthy7

9

두 개체를 직렬화하고 결과 문자열을 @JoelFan으로 비교합니다.

따라서 이렇게하려면 이와 같은 정적 클래스를 만들고 Extensions를 사용하여 모든 개체를 확장합니다 (따라서 모든 유형의 개체, 컬렉션 등을 메서드에 전달할 수 있음).

using System;
using System.IO;
using System.Runtime.Serialization.Json;
using System.Text;

public static class MySerializer
{
    public static string Serialize(this object obj)
    {
        var serializer = new DataContractJsonSerializer(obj.GetType());
        using (var ms = new MemoryStream())
        {
            serializer.WriteObject(ms, obj);
            return Encoding.Default.GetString(ms.ToArray());
        }
    }
}

다른 파일에서이 정적 클래스를 참조하면 다음을 수행 할 수 있습니다.

Person p = new Person { Firstname = "Jason", LastName = "Argonauts" };
Person p2 = new Person { Firstname = "Jason", LastName = "Argonaut" };
//assuming you have already created a class person!
string personString = p.Serialize();
string person2String = p2.Serialize();

이제 단순히 .Equals를 사용하여 비교할 수 있습니다. 개체가 컬렉션에 있는지 확인하기 위해 이것을 사용합니다. 정말 잘 작동합니다.


객체의 내용이 부동 소수점 숫자의 배열이면 어떨까요? 이를 문자열로 변환하는 것은 매우 비효율적이며 변환은 CultrureInfo. 이것은 내부 데이터가 대부분 문자열과 정수인 경우에만 작동합니다. 그렇지 않으면 재앙이 될 것입니다.
John Alexiou 2016 년

3
새로운 감독이 C #을 뜯어 내고 Python으로 교체하라고하면 어떨까요? 개발자로서 우리는 만약 질문이 어딘가에서 멈추어야한다는 것을 배워야합니다. 다음으로 문제를 해결하십시오. 만약 당신이 시간을 얻을 경우 ... 다시에 온
ozzy432836

2
Python은 구문과 사용법면에서 MATLAB과 비슷합니다. 정적 유형 안전 언어에서 python과 같은 mishmash 스크립트로 이동하는 데에는 정말 좋은 이유가 있어야합니다.
존 ALEXIOU

5

나는 당신이 말 그대로 동일한 객체를 언급하지 않는다고 가정합니다

Object1 == Object2

둘 사이의 메모리 비교를 고려할 수 있습니다.

memcmp(Object1, Object2, sizeof(Object.GetType())

그러나 그것은 C #의 실제 코드가 아닙니다. :). 모든 데이터가 힙에 생성 될 수 있기 때문에 메모리는 연속적이지 않으며 두 개체의 동일성을 불가지론적인 방식으로 비교할 수 없습니다. 사용자 지정 방식으로 한 번에 하나씩 각 값을 비교해야합니다.

IEquatable<T>클래스에 인터페이스를 추가하고 Equals유형에 대한 사용자 정의 메소드를 정의 하십시오. 그런 다음 해당 방법에서 각 값을 수동으로 테스트합니다. 가능한 IEquatable<T>경우 동봉 된 유형에 다시 추가 하고 프로세스를 반복하십시오.

class Foo : IEquatable<Foo>
{
  public bool Equals(Foo other)
  {
    /* check all the values */
    return false;
  }
}


3

나는 물체를 비교하기 위해 아래 기능을 발견했습니다.

 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 (object.Equals(Object1, default(T)) || object.Equals(object2, default(T)))
         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;
 }

나는 그것을 사용하고 있고 그것은 나를 위해 잘 작동합니다.


1
첫 번째 ifCompare(null, null) == false내가 기대하는 것과 다른 것을 의미합니다 .
Tim Sylvester

3

이미 여기에 주어진 몇 가지 답변을 바탕으로 JoelFan의 답변 을 대부분 뒷받침하기로 결정했습니다 . 나는 확장 방법을 좋아하고 다른 솔루션이 내 복잡한 클래스를 비교하는 데 사용하지 않을 때 나를 위해 잘 작동했습니다.

확장 방법

using System.IO;
using System.Xml.Serialization;

static class ObjectHelpers
{
    public static string SerializeObject<T>(this T toSerialize)
    {
        XmlSerializer xmlSerializer = new XmlSerializer(toSerialize.GetType());

        using (StringWriter textWriter = new StringWriter())
        {
            xmlSerializer.Serialize(textWriter, toSerialize);
            return textWriter.ToString();
        }
    }

    public static bool EqualTo(this object obj, object toCompare)
    {
        if (obj.SerializeObject() == toCompare.SerializeObject())
            return true;
        else
            return false;
    }

    public static bool IsBlank<T>(this T obj) where T: new()
    {
        T blank = new T();
        T newObj = ((T)obj);

        if (newObj.SerializeObject() == blank.SerializeObject())
            return true;
        else
            return false;
    }

}

사용 예

if (record.IsBlank())
    throw new Exception("Record found is blank.");

if (record.EqualTo(new record()))
    throw new Exception("Record found is blank.");

2

나는 이렇게 말할 것이다:

Object1.Equals(Object2)

당신이 찾고있는 것입니다. 물체가 동일한 지 확인하려는 경우입니다. 이것이 당신이 묻는 것처럼 보입니다.

모든 자식 개체가 동일한 지 확인하려면 Equals()메서드 를 사용하여 루프를 통해 실행하십시오 .


2
Equals의 비 참조 같음 과부하를 제공하는 경우에만.
user7116

각 클래스는 고유 한 비교 방법을 구현해야합니다. 작성자의 클래스에 Equals () 메서드에 대한 재정의가 없으면 System.Object () 클래스의 기본 메서드를 사용하므로 논리 오류가 발생합니다.
Dima

2
public class GetObjectsComparison
{
    public object FirstObject, SecondObject;
    public BindingFlags BindingFlagsConditions= BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static;
}
public struct SetObjectsComparison
{
    public FieldInfo SecondObjectFieldInfo;
    public dynamic FirstObjectFieldInfoValue, SecondObjectFieldInfoValue;
    public bool ErrorFound;
    public GetObjectsComparison GetObjectsComparison;
}
private static bool ObjectsComparison(GetObjectsComparison GetObjectsComparison)
{
    GetObjectsComparison FunctionGet = GetObjectsComparison;
    SetObjectsComparison FunctionSet = new SetObjectsComparison();
    if (FunctionSet.ErrorFound==false)
        foreach (FieldInfo FirstObjectFieldInfo in FunctionGet.FirstObject.GetType().GetFields(FunctionGet.BindingFlagsConditions))
        {
            FunctionSet.SecondObjectFieldInfo =
            FunctionGet.SecondObject.GetType().GetField(FirstObjectFieldInfo.Name, FunctionGet.BindingFlagsConditions);

            FunctionSet.FirstObjectFieldInfoValue = FirstObjectFieldInfo.GetValue(FunctionGet.FirstObject);
            FunctionSet.SecondObjectFieldInfoValue = FunctionSet.SecondObjectFieldInfo.GetValue(FunctionGet.SecondObject);
            if (FirstObjectFieldInfo.FieldType.IsNested)
            {
                FunctionSet.GetObjectsComparison =
                new GetObjectsComparison()
                {
                    FirstObject = FunctionSet.FirstObjectFieldInfoValue
                    ,
                    SecondObject = FunctionSet.SecondObjectFieldInfoValue
                };

                if (!ObjectsComparison(FunctionSet.GetObjectsComparison))
                {
                    FunctionSet.ErrorFound = true;
                    break;
                }
            }
            else if (FunctionSet.FirstObjectFieldInfoValue != FunctionSet.SecondObjectFieldInfoValue)
            {
                FunctionSet.ErrorFound = true;
                break;
            }
        }
    return !FunctionSet.ErrorFound;
}

재귀 원칙 사용.
matan justme

1

이를 수행하는 한 가지 방법은 Equals()관련된 각 유형 을 재정의 하는 것 입니다. 예를 들어 최상위 객체는 5 개의 자식 객체 모두 Equals()Equals()메서드 를 호출하도록 재정의 합니다. 이러한 개체는 모두 Equals()사용자 지정 개체라고 가정하고 재정의해야하며 , 최상위 개체에 대해 동등성 검사를 수행하여 전체 계층 구조를 비교할 수있을 때까지 계속해야합니다.



1

Jonathan의 예 덕분입니다. 모든 경우 (배열, 목록, 사전, 기본 유형)에 대해 확장했습니다.

이것은 직렬화가없는 비교이며 비교 된 객체에 대한 인터페이스 구현이 필요하지 않습니다.

        /// <summary>Returns description of difference or empty value if equal</summary>
        public static string Compare(object obj1, object obj2, string path = "")
        {
            string path1 = string.IsNullOrEmpty(path) ? "" : path + ": ";
            if (obj1 == null && obj2 != null)
                return path1 + "null != not null";
            else if (obj2 == null && obj1 != null)
                return path1 + "not null != null";
            else if (obj1 == null && obj2 == null)
                return null;

            if (!obj1.GetType().Equals(obj2.GetType()))
                return "different types: " + obj1.GetType() + " and " + obj2.GetType();

            Type type = obj1.GetType();
            if (path == "")
                path = type.Name;

            if (type.IsPrimitive || typeof(string).Equals(type))
            {
                if (!obj1.Equals(obj2))
                    return path1 + "'" + obj1 + "' != '" + obj2 + "'";
                return null;
            }
            if (type.IsArray)
            {
                Array first = obj1 as Array;
                Array second = obj2 as Array;
                if (first.Length != second.Length)
                    return path1 + "array size differs (" + first.Length + " vs " + second.Length + ")";

                var en = first.GetEnumerator();
                int i = 0;
                while (en.MoveNext())
                {
                    string res = Compare(en.Current, second.GetValue(i), path);
                    if (res != null)
                        return res + " (Index " + i + ")";
                    i++;
                }
            }
            else if (typeof(System.Collections.IEnumerable).IsAssignableFrom(type))
            {
                System.Collections.IEnumerable first = obj1 as System.Collections.IEnumerable;
                System.Collections.IEnumerable second = obj2 as System.Collections.IEnumerable;

                var en = first.GetEnumerator();
                var en2 = second.GetEnumerator();
                int i = 0;
                while (en.MoveNext())
                {
                    if (!en2.MoveNext())
                        return path + ": enumerable size differs";

                    string res = Compare(en.Current, en2.Current, path);
                    if (res != null)
                        return res + " (Index " + i + ")";
                    i++;
                }
            }
            else
            {
                foreach (PropertyInfo pi in type.GetProperties(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public))
                {
                    try
                    {
                        var val = pi.GetValue(obj1);
                        var tval = pi.GetValue(obj2);
                        if (path.EndsWith("." + pi.Name))
                            return null;
                        var pathNew = (path.Length == 0 ? "" : path + ".") + pi.Name;
                        string res = Compare(val, tval, pathNew);
                        if (res != null)
                            return res;
                    }
                    catch (TargetParameterCountException)
                    {
                        //index property
                    }
                }
                foreach (FieldInfo fi in type.GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public))
                {
                    var val = fi.GetValue(obj1);
                    var tval = fi.GetValue(obj2);
                    if (path.EndsWith("." + fi.Name))
                        return null;
                    var pathNew = (path.Length == 0 ? "" : path + ".") + fi.Name;
                    string res = Compare(val, tval, pathNew);
                    if (res != null)
                        return res;
                }
            }
            return null;
        }

코드 생성 리포지토리 를 쉽게 복사하기 위해


1

이제 json.net을 사용할 수 있습니다. Nuget으로 이동하여 설치하십시오.

그리고 다음과 같이 할 수 있습니다.

    public bool Equals(SamplesItem sampleToCompare)
    {
        string myself = JsonConvert.SerializeObject(this);
        string other = JsonConvert.SerializeObject(sampleToCompare);

        return myself == other;
    }

더 멋지게 만들고 싶다면 객체에 대한 확장 메서드를 만들 수 있습니다. 이것은 단지 공용 자산을 비교 한 것입니다. 그리고 비교를 할 때 공용 속성을 무시하려면 [JsonIgnore] 속성을 사용할 수 있습니다.


개체에 목록이 있고 목록이있는 경우 두 개체를 모두 탐색하는 것은 악몽이 될 것입니다. 둘 다 직렬화 한 다음 비교하면 해당 시나리오를 처리 할 필요가 없습니다.
ashlar64

복잡한 개체에 사전이있는 경우 .net serializer가이를 직렬화 할 수 있다고 생각하지 않습니다. Json 직렬 변환기는 할 수 있습니다.
ashlar64
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.