Beste måten å sammenligne to komplekse objekter på

 C Programming >> C C# Program >  >> C#
Beste måten å sammenligne to komplekse objekter på

Implementer IEquatable<T> (vanligvis i forbindelse med overstyring av den nedarvede Object.Equals og Object.GetHashCode metoder) på alle dine egendefinerte typer. Når det gjelder sammensatte typer, påkaller du de inneholdte typenes Equals metode innenfor de inneholdende typene. For inneholdte samlinger, bruk SequenceEqual utvidelsesmetode, som internt kaller IEquatable<T>.Equals eller Object.Equals på hvert element. Denne tilnærmingen vil åpenbart kreve at du utvider typenes definisjoner, men resultatene er raskere enn noen generiske løsninger som involverer serialisering.

Rediger :Her er et konstruert eksempel med tre nivåer av hekking.

For verdityper kan du vanligvis bare ringe Equals metode. Selv om feltene eller egenskapene aldri ble eksplisitt tildelt, ville de fortsatt ha en standardverdi.

For referansetyper bør du først ringe ReferenceEquals , som sjekker for referanselikhet – dette vil tjene som en effektivitetsøkning når du tilfeldigvis refererer til det samme objektet. Den vil også håndtere saker der begge referansene er null. Hvis denne kontrollen mislykkes, må du bekrefte at forekomstens felt eller egenskap ikke er null (for å unngå NullReferenceException ) og ring dens Equals metode. Siden medlemmene våre er riktig skrevet, er IEquatable<T>.Equals metoden kalles opp direkte, og omgår den overstyrte Object.Equals metode (hvis utførelse ville være marginalt tregere på grunn av typebesetningen).

Når du overstyrer Object.Equals , forventes du også å overstyre Object.GetHashCode; Jeg gjorde det ikke nedenfor for konsis skyld.

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

Oppdater :Dette svaret ble skrevet for flere år siden. Siden den gang har jeg begynt å lene meg unna å implementere IEquality<T> for mutbare typer for slike scenarier. Det er to forestillinger om likhet:identitet og ekvivalens . På et minnerepresentasjonsnivå skilles disse populært ut som «referanselikhet» og «verdilikhet» (se Likestillingssammenlikninger). Det samme skillet kan imidlertid også gjelde på domenenivå. Anta at Person klasse har en PersonId eiendom, unik per distinkt person i den virkelige verden. Skal to objekter med samme PersonId men annerledes Age verdier anses like eller forskjellige? Svaret ovenfor forutsetter at man er ute etter ekvivalens. Det er imidlertid mange bruksområder for IEquality<T> grensesnitt, for eksempel samlinger, som forutsetter at slike implementeringer sørger for identitet . For eksempel hvis du fyller ut en HashSet<T> , vil du vanligvis forvente en TryGetValue(T,T) kall for å returnere eksisterende elementer som bare deler identiteten til argumentet ditt, ikke nødvendigvis likeverdige elementer hvis innhold er helt det samme. Denne oppfatningen håndheves av merknadene om GetHashCode :


Serialiser begge objektene og sammenlign de resulterende strengene


Du kan bruke utvidelsesmetoden, rekursjon for å løse dette problemet:

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

eller sammenlign ved å bruke Json (hvis objektet er veldig komplekst) Du kan bruke 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;
}