Cách tốt nhất để so sánh hai đối tượng phức tạp


112

Tôi có hai đối tượng phức tạp như Object1Object2. Chúng có khoảng 5 cấp độ đối tượng con.

Tôi cần phương pháp nhanh nhất để biết chúng có giống nhau hay không.

Làm thế nào điều này có thể được thực hiện trong C # 4.0?

Câu trả lời:


101

Triển khai IEquatable<T>(thường kết hợp với ghi đè các phương thức Object.Equalsvà kế thừa Object.GetHashCode) trên tất cả các loại tùy chỉnh của bạn. Trong trường hợp các kiểu kết hợp, hãy gọi Equalsphương thức kiểu chứa trong các kiểu chứa. Đối với các bộ sưu tập được chứa, hãy sử dụng SequenceEqualphương thức mở rộng, gọi nội bộ IEquatable<T>.Equalshoặc Object.Equalstrên từng phần tử. Cách tiếp cận này rõ ràng sẽ yêu cầu bạn mở rộng định nghĩa các kiểu của mình, nhưng kết quả của nó nhanh hơn bất kỳ giải pháp chung chung nào liên quan đến tuần tự hóa.

Chỉnh sửa : Đây là một ví dụ có sẵn với ba cấp độ lồng vào nhau.

Đối với các loại giá trị, thông thường bạn có thể chỉ cần gọi Equalsphương thức của chúng . Ngay cả khi các trường hoặc thuộc tính không bao giờ được gán rõ ràng, chúng vẫn sẽ có giá trị mặc định.

Đối với các loại tham chiếu, trước tiên bạn nên gọi ReferenceEqualsđể kiểm tra sự bình đẳng tham chiếu - điều này sẽ đóng vai trò như một sự tăng hiệu quả khi bạn tình cờ tham chiếu đến cùng một đối tượng. Nó cũng sẽ xử lý các trường hợp cả hai tham chiếu đều rỗng. Nếu việc kiểm tra đó không thành công, hãy xác nhận rằng trường hoặc thuộc tính của cá thể của bạn không phải là rỗng (để tránh NullReferenceException) và gọi Equalsphương thức của nó . Vì các thành viên của chúng ta được nhập đúng cách, nên IEquatable<T>.Equalsphương thức được gọi trực tiếp, bỏ qua Object.Equalsphương thức bị ghi đè (mà việc thực thi sẽ chậm hơn một chút do kiểu ép kiểu).

Khi bạn ghi đè Object.Equals, bạn cũng sẽ ghi đè lên Object.GetHashCode; Tôi đã không làm như vậy dưới đây vì lợi ích ngắn gọn.

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

Cập nhật : Câu trả lời này đã được viết vài năm trước. Kể từ đó, tôi bắt đầu bỏ qua việc triển khai IEquality<T>các loại có thể thay đổi cho các tình huống như vậy. Có hai khái niệm bình đẳng: bản sắc và sự tương đương . Ở cấp độ biểu diễn bộ nhớ, chúng được phân biệt phổ biến là “bình đẳng tham chiếu” và “bình đẳng giá trị” (xem So sánh bình đẳng ). Tuy nhiên, sự phân biệt tương tự cũng có thể áp dụng ở cấp miền. Giả sử rằng Personlớp của bạn có một thuộc PersonIdtính, duy nhất cho mỗi người trong thế giới thực riêng biệt. Hai đối tượng có cùng giá trị PersonIdnhưng khác Agegiá trị nên được coi là bằng nhau hay khác nhau? Câu trả lời ở trên giả định rằng một là sau tương đương. Tuy nhiên, có nhiều cách sử dụngIEquality<T>giao diện, chẳng hạn như bộ sưu tập, giả định rằng việc triển khai như vậy cung cấp danh tính . Ví dụ: nếu bạn đang điền một HashSet<T>, bạn thường mong đợi một TryGetValue(T,T)lệnh gọi trả về các phần tử hiện có chỉ chia sẻ danh tính của đối số của bạn, không nhất thiết là các phần tử tương đương có nội dung hoàn toàn giống nhau. Khái niệm này được thực thi bởi các ghi chú về GetHashCode:

Nói chung, đối với các loại tham chiếu có thể thay đổi, bạn chỉ nên ghi đè GetHashCode()khi:

  • Bạn có thể tính toán mã băm từ các trường không thể thay đổi; hoặc là
  • Bạn có thể đảm bảo rằng mã băm của một đối tượng có thể thay đổi không thay đổi trong khi đối tượng được chứa trong một bộ sưu tập dựa trên mã băm của nó.

Tôi lấy đối tượng này thông qua Dịch vụ RIA ... Tôi có thể sử dụng IEquatable <Foo> cho các đối tượng đó và lấy đối tượng này trong ứng dụng khách WPF không?
Nhà phát triển

1
Ý bạn là các lớp được tạo tự động? Tôi chưa sử dụng Dịch vụ RIA, nhưng tôi sẽ giả định rằng bất kỳ lớp nào được tạo sẽ được khai báo là partial- trong trường hợp đó, có, bạn có thể triển khai Equalsphương thức của chúng thông qua khai báo lớp từng phần được thêm theo cách thủ công, tham chiếu đến các trường / thuộc tính từ lớp được tạo tự động một.
Douglas

Điều gì xảy ra nếu "Địa chỉ địa chỉ" thực sự là "Địa chỉ [] địa chỉ", nó sẽ được triển khai như thế nào?
guiomie

2
Bạn có thể gọi LINQ Enumerable.SequenceEqualphương pháp trên các mảng: this.Addresses.SequenceEqual(other.Addresses). Điều này sẽ gọi nội bộ Address.Equalsphương thức của bạn cho từng cặp địa chỉ tương ứng, với điều kiện là Addresslớp thực hiện IEquatable<Address>giao diện.
Douglas

2
Một danh mục so sánh khác mà nhà phát triển có thể kiểm tra là "WorksLike". Đối với tôi, điều này có nghĩa là mặc dù hai trường hợp có thể có một số giá trị thuộc tính không bằng nhau, chương trình sẽ tạo ra cùng một kết quả khi xử lý hai trường hợp.
John Kurtz

95

Tuần tự hóa cả hai đối tượng và so sánh các chuỗi kết quả


1
Tôi không hiểu tại sao sẽ có. Serialization thường là một quá trình được tối ưu hóa và bạn cần phải truy cập vào mọi giá trị của thuộc tính trong mọi trường hợp.
JoelFan

5
Có một chi phí lớn. Bạn đang tạo một luồng dữ liệu, nối các chuỗi và sau đó kiểm tra tính bình đẳng của chuỗi. Đơn hàng của độ lớn, chỉ trong đó. Chưa kể tuần tự hóa sẽ được sử dụng phản chiếu theo mặc định.
Jerome Haltom

2
Luồng dữ liệu không phải là vấn đề lớn, tôi không hiểu tại sao bạn cần phải nối thêm các chuỗi ... kiểm tra tính bình đẳng chuỗi là một trong những hoạt động được tối ưu hóa nhất hiện có .... bạn có thể có ý kiến ​​phản ánh ... nhưng trên toàn bộ tuần tự hóa sẽ không tệ hơn các phương pháp khác. Bạn nên làm tiêu chuẩn nếu bạn nghi ngờ vấn đề hiệu suất ... Tôi có vấn đề hiệu suất không có kinh nghiệm với phương pháp này
JoelFan

12
Tôi là +1như vậy đơn giản vì tôi chưa bao giờ nghĩ đến việc so sánh bình đẳng dựa trên giá trị theo cách này. Nó đẹp và đơn giản. Sẽ rất dễ dàng nếu bạn thấy một số điểm chuẩn với mã này.
Thomas

1
Đó không phải là một giải pháp tốt vì cả hai quá trình tuần tự hóa có thể bị sai theo cách giống nhau. Ví dụ, một số thuộc tính của đối tượng nguồn có thể chưa được tuần tự hóa và khi giải mã hóa sẽ được đặt thành null trong đối tượng đích. Trong trường hợp như vậy, bài kiểm tra so sánh các chuỗi của bạn sẽ vượt qua, nhưng cả hai đối tượng thực sự không giống nhau!
stackMeUp

35

Bạn có thể sử dụng phương pháp mở rộng, đệ quy để giải quyết vấn đề này:

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

hoặc so sánh bằng cách sử dụng Json (nếu đối tượng rất phức tạp) Bạn có thể sử dụng 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
Giải pháp đầu tiên là tuyệt vời! Tôi thích rằng bạn không cần phải tuần tự hóa json hoặc thực hiện thêm bất kỳ mã nào vào chính các đối tượng. Thích hợp khi bạn chỉ so sánh cho các bài kiểm tra đơn vị. Tôi có thể đề xuất thêm một phép so sánh đơn giản trong trường hợp objValue và anotherValue đều bằng rỗng không? Điều này sẽ tránh NullReferenceException được ném ra khi cố gắng thực hiện null.Equals () // ReSharper vô hiệu hóa sau khi RedundantJumpStatement if (objValue == anotherValue) continue; // bảo vệ tham chiếu null else if (! objValue.Equals (anotherValue)) Fail (mong đợi, thực tế);
Mark Conway

3
Có lý do gì để sử dụng DeepComparethay vì chỉ gọi CompareExđệ quy không?
Apoolov

3
Điều này có thể so sánh toàn bộ cấu trúc một cách không cần thiết. Thay thế resultbằng return falsesẽ làm cho nó hiệu quả hơn.
Tim Sylvester

24

Nếu bạn không muốn triển khai IEquatable, bạn luôn có thể sử dụng Reflection để so sánh tất cả các thuộc tính: - nếu chúng là kiểu giá trị, chỉ cần so sánh chúng - nếu chúng là kiểu tham chiếu, hãy gọi hàm đệ quy để so sánh các thuộc tính "bên trong" của nó .

Tôi không nghĩ về hiệu suất, mà là về sự đơn giản. Tuy nhiên, nó phụ thuộc vào thiết kế chính xác của các đối tượng của bạn. Nó có thể trở nên phức tạp tùy thuộc vào hình dạng đối tượng của bạn (ví dụ: nếu có sự phụ thuộc tuần hoàn giữa các thuộc tính). Tuy nhiên, có một số giải pháp ngoài kia mà bạn có thể sử dụng, như giải pháp sau:

Một tùy chọn khác là tuần tự hóa đối tượng dưới dạng văn bản, chẳng hạn như sử dụng JSON.NET và so sánh kết quả tuần tự hóa. (JSON.NET có thể xử lý các phụ thuộc theo chu kỳ giữa các thuộc tính).

Tôi không biết ý bạn là cách nhanh nhất để triển khai nó hay một đoạn mã chạy nhanh. Bạn không nên tối ưu hóa trước khi biết nếu bạn cần. Tối ưu hóa sớm là gốc rễ của mọi điều xấu


1
Tôi hầu như không nghĩ rằng một IEquatable<T>triển khai đủ điều kiện là một trường hợp tối ưu hóa sớm. Phản xạ sẽ chậm hơn đáng kể. Việc triển khai mặc định Equalscho các loại giá trị tùy chỉnh sử dụng phản chiếu; Bản thân Microsoft khuyên bạn nên ghi đè Equalsphương thức cho một kiểu cụ thể để cải thiện hiệu suất của phương pháp và thể hiện chặt chẽ hơn khái niệm bình đẳng cho kiểu. ”
Douglas

1
Nó phụ thuộc vào số lần anh ta sẽ chạy phương pháp bằng: 1, 10, 100, 100, một triệu? Điều đó sẽ tạo ra sự khác biệt lớn. Nếu anh ta có thể sử dụng một giải pháp chung chung mà không cần thực hiện bất cứ điều gì, anh ta sẽ dành chút thời gian quý báu. Nếu quá chậm, thì đã đến lúc triển khai IEquatable (và thậm chí có thể cố gắng tạo một GetHashCode có thể lưu vào bộ nhớ cache hoặc thông minh) Theo như tốc độ của Reflection, tôi phải đồng ý rằng nó chậm hơn ... hoặc chậm hơn nhiều, tùy thuộc vào cách bạn thực hiện ( tức là sử dụng lại các Loại PropertyInfos, v.v., hoặc không).
JotaBe

@ Worthy7 Nó có. Xin vui lòng, xem nội dung của dự án. Kiểm tra là một cách tốt để ghi lại bằng ví dụ. Nhưng tốt hơn hết, nếu bạn tìm kiếm nó, bạn sẽ tìm thấy một tệp trợ giúp .chm. Vì vậy, dự án này có tài liệu tốt hơn nhiều so với hầu hết các dự án.
JotaBe

Xin lỗi bạn nói đúng, tôi hoàn toàn bỏ lỡ tab "wiki". Tôi đã quen với việc mọi người viết mọi thứ trong readme.
Worthy7

9

Tuần tự hóa cả hai đối tượng và so sánh các chuỗi kết quả bằng @JoelFan

Vì vậy, để thực hiện việc này, hãy tạo một lớp tĩnh như vậy và sử dụng Phần mở rộng để mở rộng TẤT CẢ các đối tượng (để bạn có thể chuyển bất kỳ loại đối tượng, bộ sưu tập, v.v. vào phương thức)

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

Khi bạn tham chiếu lớp tĩnh này trong bất kỳ tệp nào khác, bạn có thể thực hiện việc này:

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

Bây giờ bạn có thể chỉ cần sử dụng .Equals để so sánh chúng. Tôi sử dụng điều này để kiểm tra xem các đối tượng cũng có trong bộ sưu tập hay không. Nó hoạt động thực sự tốt.


Điều gì sẽ xảy ra nếu nội dung của các đối tượng là mảng số dấu phẩy động. Sẽ rất kém hiệu quả khi chuyển đổi chúng thành chuỗi và việc chuyển đổi phụ thuộc vào các phép biến đổi được xác định trong CultrureInfo. Điều này sẽ chỉ hoạt động nếu dữ liệu nội bộ chủ yếu là chuỗi và số nguyên. Nếu không nó sẽ là một thảm họa.
John Alexiou

3
Điều gì sẽ xảy ra nếu một giám đốc mới yêu cầu bạn tách C # và thay thế nó bằng Python. Với tư cách là nhà phát triển, chúng ta cần biết rằng điều gì xảy ra nếu các câu hỏi phải dừng lại ở đâu đó. Giải quyết vấn đề, tiếp theo. Nếu bạn đã từng có thời gian, trở lại với nó ...
ozzy432836

2
Python giống MATLAB hơn về cú pháp và cách sử dụng. Phải có một lý do thực sự chính đáng để chuyển từ ngôn ngữ an toàn kiểu tĩnh sang một tập lệnh mishmash như python.
John Alexiou

5

Tôi sẽ cho rằng bạn không đề cập đến các đối tượng giống nhau theo nghĩa đen

Object1 == Object2

Bạn có thể đang nghĩ đến việc so sánh trí nhớ giữa hai

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

Nhưng đó thậm chí không phải là mã thực trong c # :). Bởi vì tất cả dữ liệu của bạn có thể được tạo trên heap, bộ nhớ không liền kề và bạn không thể chỉ so sánh sự bằng nhau của hai đối tượng theo cách bất khả tri. Bạn sẽ phải so sánh từng giá trị, từng giá trị một, theo cách tùy chỉnh.

Cân nhắc thêm IEquatable<T>giao diện vào lớp của bạn và xác định Equalsphương thức tùy chỉnh cho kiểu của bạn. Sau đó, trong phương pháp đó, hãy kiểm tra thủ công từng giá trị. Thêm IEquatable<T>một lần nữa vào các loại kèm theo nếu bạn có thể và lặp lại quy trình.

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


3

Tôi tìm thấy chức năng này bên dưới để so sánh các đối tượng.

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

Tôi đang sử dụng nó và nó đang hoạt động tốt đối với tôi.


1
Điều đầu tiên ifcó nghĩa là Compare(null, null) == falseđó không phải là những gì tôi mong đợi.
Tim Sylvester

3

Dựa trên một số câu trả lời đã được đưa ra ở đây, tôi quyết định chủ yếu quay lại câu trả lời của JoelFan . Tôi thích các phương thức mở rộng và những phương pháp này đã hoạt động rất hiệu quả đối với tôi khi không có giải pháp nào khác sử dụng chúng để so sánh các lớp phức tạp của tôi.

Phương pháp mở rộng

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

}

Ví dụ sử dụng

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

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

2

Tôi sẽ nói rằng:

Object1.Equals(Object2)

sẽ là những gì bạn đang tìm kiếm. Đó là nếu bạn đang muốn xem các đối tượng có giống nhau hay không, đó là điều bạn có vẻ đang hỏi.

Nếu bạn muốn kiểm tra xem tất cả các đối tượng con có giống nhau hay không, hãy chạy chúng qua một vòng lặp với Equals()phương thức.


2
Nếu và chỉ khi chúng cung cấp quá tải đẳng thức không tham chiếu của Equals.
user7116

Mỗi lớp phải thực hiện cách so sánh riêng của mình. Nếu các lớp của tác giả không có ghi đè cho phương thức Equals (), chúng sẽ sử dụng phương thức cơ bản của lớp System.Object (), điều này sẽ dẫn đến lỗi logic.
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;
}

Sử dụng các nguyên tắc đệ quy.
matan justme

1

Một cách để làm điều này là ghi đè lên Equals()từng loại liên quan. Ví dụ: đối tượng cấp cao nhất của bạn sẽ ghi đè Equals()để gọi Equals()phương thức của tất cả 5 đối tượng con. Tất cả các đối tượng đó cũng nên ghi đè Equals(), giả sử chúng là các đối tượng tùy chỉnh, v.v. cho đến khi toàn bộ hệ thống phân cấp có thể được so sánh bằng cách chỉ thực hiện kiểm tra bình đẳng trên các đối tượng cấp cao nhất.



1

Cảm ơn tấm gương của Jonathan. Tôi đã mở rộng nó cho tất cả các trường hợp (mảng, danh sách, từ điển, kiểu nguyên thủy).

Đây là một so sánh không có tuần tự hóa và không yêu cầu thực hiện bất kỳ giao diện nào cho các đối tượng được so sánh.

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

Để sao chép dễ dàng kho lưu trữ mã đã tạo


1

Bây giờ bạn có thể sử dụng json.net. Chỉ cần vào Nuget và cài đặt nó.

Và bạn có thể làm điều gì đó như sau:

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

        return myself == other;
    }

Có lẽ bạn có thể tạo một phương thức mở rộng cho đối tượng nếu bạn muốn có được những cái đẹp hơn. Xin lưu ý rằng điều này chỉ so sánh các tài sản công cộng. Và nếu bạn muốn bỏ qua thuộc tính công khai khi thực hiện so sánh, bạn có thể sử dụng thuộc tính [JsonIgnore].


Nếu bạn có danh sách trong các đối tượng của mình và những đối tượng đó có danh sách thì việc cố gắng lướt qua cả hai đối tượng sẽ là một cơn ác mộng. Nếu bạn tuần tự hóa cả hai và sau đó so sánh, bạn sẽ không phải đối phó với tình huống đó.
ashlar64

Nếu đối tượng phức tạp của bạn có một Từ điển trong đó, tôi không tin rằng trình tuần tự .net có thể tuần tự hóa nó. Bộ nối tiếp Json có thể.
ashlar64
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.