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

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

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

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



답변

모든 사용자 지정 형식에 대해 구현 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()경우 다음과 같은 경우에만 재정의해야 합니다.

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

답변

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


답변

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

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


답변

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

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

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

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


답변

두 개체를 직렬화하고 결과 문자열을 @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를 사용하여 비교할 수 있습니다. 개체가 컬렉션에 있는지 확인하기 위해 이것을 사용합니다. 정말 잘 작동합니다.


답변

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

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


답변

두 객체를 직렬화 한 다음 해시 코드를 계산 한 다음 비교합니다.