วิธีที่ดีที่สุดในการเปรียบเทียบวัตถุที่ซับซ้อนสองชิ้น


113

ฉันมีสองวัตถุที่ซับซ้อนเช่นและObject1 พวกเขามีวัตถุเด็กประมาณ 5 ระดับObject2

ฉันต้องการวิธีที่เร็วที่สุดเพื่อบอกว่ามันเหมือนกันหรือไม่

จะทำได้อย่างไรใน C # 4.0?

คำตอบ:


101

ใช้งานIEquatable<T>(โดยทั่วไปจะใช้ร่วมกับการลบล้างสิ่งที่สืบทอดมาObject.EqualsและObject.GetHashCodeวิธีการ) ในประเภทที่กำหนดเองทั้งหมดของคุณ ในกรณีของประเภทคอมโพสิตให้เรียกใช้Equalsเมธอดประเภทที่มีอยู่ภายในประเภทที่มี สำหรับคอลเล็กชันที่มีอยู่ให้ใช้SequenceEqualวิธีการขยายซึ่งเรียกใช้ภายในIEquatable<T>.EqualsหรือObject.Equalsในแต่ละองค์ประกอบ เห็นได้ชัดว่าวิธีนี้ต้องการให้คุณขยายคำจำกัดความของประเภทของคุณ แต่ผลลัพธ์ของมันจะเร็วกว่าโซลูชันทั่วไปที่เกี่ยวข้องกับการทำให้เป็นอนุกรม

แก้ไข : นี่คือตัวอย่างที่สร้างขึ้นโดยมีการซ้อนกันสามระดับ

สำหรับประเภทค่าโดยทั่วไปคุณสามารถเรียกEqualsใช้เมธอดได้ แม้ว่าเขตข้อมูลหรือคุณสมบัติไม่เคยกำหนดอย่างชัดเจน แต่ก็ยังคงมีค่าเริ่มต้น

สำหรับประเภทการอ้างอิงอันดับแรกคุณควรเรียกReferenceEqualsซึ่งตรวจสอบความเท่าเทียมกันของการอ้างอิงซึ่งจะช่วยเพิ่มประสิทธิภาพเมื่อคุณอ้างถึงวัตถุเดียวกัน นอกจากนี้ยังจัดการกรณีที่การอ้างอิงทั้งสองเป็นโมฆะ หากการตรวจสอบล้มเหลวให้ยืนยันว่าฟิลด์หรือคุณสมบัติของอินสแตนซ์ของคุณไม่เป็นโมฆะ (เพื่อหลีกเลี่ยง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>อินเตอร์เฟซเช่นคอลเลกชันที่คิดว่าการใช้งานดังกล่าวจัดให้มีตัวตน ตัวอย่างเช่นหากคุณกำลังเติมข้อมูลHashSet<T>โดยทั่วไปคุณจะคาดหวังว่าจะมีการTryGetValue(T,T)เรียกคืนองค์ประกอบที่มีอยู่ซึ่งใช้ร่วมกันเพียงข้อมูลประจำตัวของอาร์กิวเมนต์ของคุณเท่านั้นไม่จำเป็นต้องเทียบเท่าองค์ประกอบที่มีเนื้อหาเหมือนกันทั้งหมด ความคิดนี้บังคับใช้โดยหมายเหตุเกี่ยวกับGetHashCode:

โดยทั่วไปสำหรับประเภทการอ้างอิงที่เปลี่ยนแปลงได้คุณควรแทนที่GetHashCode()เฉพาะในกรณีที่:

  • คุณสามารถคำนวณรหัสแฮชจากฟิลด์ที่ไม่สามารถเปลี่ยนแปลงได้ หรือ
  • คุณสามารถมั่นใจได้ว่าโค้ดแฮชของอ็อบเจ็กต์ที่เปลี่ยนแปลงไม่ได้จะไม่เปลี่ยนแปลงในขณะที่อ็อบเจ็กต์อยู่ในคอลเล็กชันที่อาศัยโค้ดแฮช

ฉันได้รับวัตถุนี้ผ่านบริการ RIA ... ฉันสามารถใช้ IEquatable <Foo> สำหรับวัตถุเหล่านั้นและรับวัตถุนั้นภายใต้ไคลเอนต์ WPF ได้หรือไม่
พัฒนา

1
คุณหมายความว่าชั้นเรียนสร้างขึ้นโดยอัตโนมัติหรือไม่? ฉันไม่ได้ใช้บริการ RIA แต่ฉันคิดว่าคลาสที่สร้างขึ้นจะถูกประกาศเป็นpartial- ในกรณีนี้ใช่คุณสามารถใช้Equalsวิธีการของพวกเขาผ่านการประกาศคลาสบางส่วนที่เพิ่มด้วยตนเองซึ่งอ้างอิงฟิลด์ / คุณสมบัติจากการสร้างอัตโนมัติ หนึ่ง.
ดักลาส

จะเกิดอะไรขึ้นหาก "ที่อยู่ที่อยู่" เป็น "ที่อยู่ [] ที่อยู่" จริงจะดำเนินการอย่างไร
guiomie

2
คุณสามารถเรียก LINQ วิธีการในอาร์เรย์:Enumerable.SequenceEqual this.Addresses.SequenceEqual(other.Addresses)สิ่งนี้จะเรียกAddress.Equalsวิธีการของคุณภายในสำหรับแต่ละคู่ของที่อยู่ที่เกี่ยวข้องเนื่องจากAddressคลาสนั้นใช้IEquatable<Address>อินเทอร์เฟซ
Douglas

2
การเปรียบเทียบอีกหมวดหนึ่งที่นักพัฒนาอาจตรวจสอบคือ "WorksLike" สำหรับฉันนี่หมายความว่าแม้ว่าอินสแตนซ์สองอินสแตนซ์อาจมีค่าคุณสมบัติที่ไม่เท่ากัน แต่โปรแกรมจะให้ผลลัพธ์เดียวกันจากการประมวลผลอินสแตนซ์ทั้งสอง
John Kurtz

95

ทำให้วัตถุทั้งสองเป็นอนุกรมและเปรียบเทียบสตริงผลลัพธ์


1
ฉันไม่เห็นว่าทำไมจะมี โดยปกติการทำให้เป็นอนุกรมเป็นกระบวนการที่ได้รับการปรับให้เหมาะสมและคุณจำเป็นต้องเข้าถึงทุกค่าของคุณสมบัติไม่ว่าในกรณีใด ๆ
JoelFan

5
มีค่าใช้จ่ายมหาศาล คุณกำลังสร้างสตรีมข้อมูลต่อท้ายสตริงจากนั้นทดสอบความเท่าเทียมกันของสตริง ลำดับขนาดก็แค่นั้น ไม่ต้องพูดถึงการทำให้เป็นอนุกรมจะใช้การสะท้อนเป็นค่าเริ่มต้น
Jerome Haltom

2
สตรีมข้อมูลไม่ใช่เรื่องใหญ่ฉันไม่เห็นว่าทำไมคุณต้องต่อท้ายสตริง ... การทดสอบความเท่าเทียมกันของสตริงเป็นหนึ่งในการดำเนินการที่เหมาะสมที่สุดที่นั่น .... คุณอาจมีประเด็นที่สะท้อน ... แต่ใน การทำให้เป็นอนุกรมทั้งหมดจะไม่เป็น "ลำดับขนาด" ที่แย่ไปกว่าวิธีอื่น ๆ คุณควรทำเกณฑ์มาตรฐานหากคุณสงสัยว่ามีปัญหาด้านประสิทธิภาพ ... ฉันไม่พบปัญหาด้านประสิทธิภาพด้วยวิธีนี้
JoelFan

12
ฉันเป็น+1เช่นนี้เพราะฉันไม่เคยคิดเกี่ยวกับการเปรียบเทียบความเท่าเทียมตามค่านิยมด้วยวิธีนี้ มันดีและเรียบง่าย มันเป็นเรื่องปกติที่จะเห็นการเปรียบเทียบกับรหัสนี้
โธมัส

1
นั่นไม่ใช่วิธีแก้ปัญหาที่ดีเนื่องจากการทำให้เป็นอนุกรมทั้งสองอาจผิดพลาดในลักษณะเดียวกัน ตัวอย่างเช่นคุณสมบัติบางอย่างของวัตถุต้นทางอาจไม่ได้รับการทำให้เป็นอนุกรมและเมื่อ deserialized จะถูกตั้งค่าเป็น 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 เท่ากันหรือไม่ สิ่งนี้จะหลีกเลี่ยงไม่ให้ NullReferenceException ถูกโยนเมื่อพยายามทำ null.Equals () // ReSharper ปิดการใช้งานเมื่อ RedundantJumpStatement ถ้า (objValue == anotherValue) ดำเนินการต่อ; // ตัวป้องกันการอ้างอิงเป็นโมฆะอื่นถ้า (! objValue.Equals (anotherValue)) ล้มเหลว (คาดว่าจะเกิดขึ้นจริง);
Mark Conway

3
มีเหตุผลใดบ้างที่จะใช้DeepCompareแทนการโทรCompareExซ้ำ ๆ ?
apostolov

3
ซึ่งอาจเปรียบเทียบโครงสร้างทั้งหมดโดยไม่จำเป็น การแทนที่resultด้วยreturn falseจะทำให้มีประสิทธิภาพมากขึ้น
Tim Sylvester

24

หากคุณไม่ต้องการใช้ IEquatable คุณสามารถใช้ Reflection เพื่อเปรียบเทียบคุณสมบัติทั้งหมดได้เสมอ: - หากเป็นประเภทค่าให้เปรียบเทียบ - หากเป็นประเภทอ้างอิงให้เรียกใช้ฟังก์ชันซ้ำเพื่อเปรียบเทียบคุณสมบัติ "ภายใน" .

ฉันไม่ได้คิดเกี่ยวกับการแสดง แต่เกี่ยวกับความเรียบง่าย อย่างไรก็ตามขึ้นอยู่กับการออกแบบที่แน่นอนของวัตถุของคุณ อาจมีความซับซ้อนขึ้นอยู่กับรูปร่างวัตถุของคุณ (ตัวอย่างเช่นหากมีการอ้างอิงแบบวงกลมระหว่างคุณสมบัติ) อย่างไรก็ตามมีวิธีแก้ปัญหามากมายที่คุณสามารถใช้ได้เช่นนี้:

อีกทางเลือกหนึ่งคือการทำให้วัตถุเป็นอนุกรมเป็นข้อความเช่นใช้ JSON.NET และเปรียบเทียบผลการทำให้เป็นอนุกรม (JSON.NET สามารถจัดการการอ้างอิงวงจรระหว่างคุณสมบัติ)

ฉันไม่รู้ว่าโดยเร็วคุณหมายถึงวิธีที่เร็วที่สุดในการติดตั้งหรือโค้ดที่ทำงานเร็ว คุณไม่ควรปรับให้เหมาะสมก่อนที่จะรู้ว่าคุณจำเป็นต้องทำ การเพิ่มประสิทธิภาพก่อนวัยเป็นรากเหง้าของความชั่วร้ายทั้งหมด


1
ฉันแทบไม่คิดว่าIEquatable<T>การนำไปใช้งานมีคุณสมบัติเป็นกรณีของการเพิ่มประสิทธิภาพก่อนกำหนด การสะท้อนแสงจะช้าลงอย่างมาก การใช้ค่าเริ่มต้นEqualsสำหรับชนิดค่าที่กำหนดเองจะใช้การสะท้อน ไมโครซอฟท์เองแนะนำให้ลบล้างวิธีการนี้เพื่อประสิทธิภาพ:“ แทนที่Equalsวิธีการสำหรับประเภทใดประเภทหนึ่งเพื่อปรับปรุงประสิทธิภาพของวิธีการและแสดงแนวคิดเรื่องความเท่าเทียมกันของประเภทอย่างใกล้ชิดยิ่งขึ้น”
ดักลาส

1
ขึ้นอยู่กับว่าเขาจะเรียกใช้วิธีการเท่ากับกี่ครั้ง: 1, 10, 100, 100, ล้าน? นั่นจะสร้างความแตกต่างที่ยิ่งใหญ่ หากเขาสามารถใช้โซลูชันทั่วไปโดยไม่ต้องใช้อะไรเลยเขาจะเผื่อเวลาอันมีค่าไว้ หากช้าเกินไปก็ถึงเวลาใช้ IEquatable (และบางทีอาจพยายามสร้าง GetHashCode ที่แคชได้หรือชาญฉลาด) เท่าที่ความเร็วของการสะท้อนกลับฉันต้องยอมรับว่าช้ากว่า ... หรือช้ากว่ามากขึ้นอยู่กับว่าคุณทำอย่างไร ( เช่นการนำ PropertyInfos types มาใช้ซ้ำเป็นต้นหรือไม่)
JotaBe

@ Worthy7 มันทำ. โปรดดูเนื้อหาของโครงการ การทดสอบเป็นวิธีที่ดีในการจัดทำเอกสารโดยใช้ตัวอย่าง แต่ที่ดีไปกว่านั้นถ้าคุณมองหามันคุณจะพบไฟล์วิธีใช้. chm ดังนั้นโครงการนี้จึงมีเอกสารประกอบที่ดีกว่าโครงการส่วนใหญ่มาก
JotaBe

ขออภัยคุณพูดถูกฉันพลาดแท็บ "wiki" ไปโดยสิ้นเชิง ฉันคุ้นเคยกับทุกคนที่เขียนสิ่งต่างๆใน readme
สมควร 7

9

ทำให้วัตถุทั้งสองเป็นลำดับและเปรียบเทียบสตริงผลลัพธ์โดย @JoelFan

ในการทำสิ่งนี้ให้สร้างคลาสแบบคงที่เช่นนั้นและใช้ส่วนขยายเพื่อขยายอ็อบเจ็กต์ทั้งหมด (เพื่อให้คุณสามารถส่งประเภทของอ็อบเจ็กต์คอลเลคชันและอื่น ๆ ลงในเมธอด)

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

3
จะเกิดอะไรขึ้นถ้าผู้กำกับคนใหม่บอกให้คุณตัด C # ออกและแทนที่ด้วย Python ในฐานะนักพัฒนาเราจำเป็นต้องเรียนรู้ว่าจะเกิดอะไรขึ้นถ้าคำถามต้องหยุดอยู่ที่ไหนสักแห่ง แก้ไขปัญหาในขั้นต่อไป ถ้าคุณเคยมีเวลากลับมา ...
ozzy432836

2
Python เป็นเหมือน MATLAB ในรูปแบบไวยากรณ์และการใช้งาน ต้องมีเหตุผลที่ดีจริงๆในการย้ายจากภาษาเซฟประเภทคงที่ไปยังสคริปต์ที่ไม่ชัดเจนเช่น python
John 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
อย่างแรกifหมายความว่านั่นCompare(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()แต่ละประเภทที่เกี่ยวข้อง ตัวอย่างเช่นออบเจ็กต์ระดับบนสุดของคุณจะแทนที่Equals()เพื่อเรียกEquals()เมธอดของอ็อบเจ็กต์ลูกทั้ง 5 อ็อบเจ็กต์เหล่านั้นควรลบล้างทั้งหมดEquals()เช่นกันโดยสมมติว่าเป็นอ็อบเจ็กต์ที่กำหนดเองและอื่น ๆ จนกว่าลำดับชั้นทั้งหมดจะสามารถเปรียบเทียบได้โดยทำการตรวจสอบความเท่าเทียมกันบนอ็อบเจ็กต์ระดับบนสุด



1

ขอบคุณตัวอย่างของโจนาธาน ฉันขยายมันสำหรับทุกกรณี (อาร์เรย์รายการพจนานุกรมประเภทดั้งเดิม)

นี่คือการเปรียบเทียบโดยไม่ต้องทำให้เป็นอนุกรมและไม่จำเป็นต้องใช้อินเทอร์เฟซใด ๆ สำหรับวัตถุที่เปรียบเทียบ

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

หากวัตถุที่ซับซ้อนของคุณมี Dictionary อยู่ในนั้นฉันไม่เชื่อว่า. net serializer สามารถทำให้เป็นอนุกรมได้ Json serializer สามารถ
ashlar64
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.