2つの複雑なオブジェクトを比較する最良の方法


112

とのような2つの複雑なオブジェクトがObject1ありObject2ます。彼らは約5レベルの子オブジェクトを持っています。

それらが同じかどうかを判断するには、最速の方法が必要です。

これはC#4.0でどのように実行できますか?

回答:


101

すべてのカスタムタイプに実装しますIEquatable<T>(通常、継承さObject.EqualsれたObject.GetHashCodeメソッドとメソッドのオーバーライドと組み合わせて)。複合型の場合はEquals、包含型内で包含型のメソッドを呼び出します。含まれているコレクションの場合、SequenceEqual拡張メソッドを使用します。このメソッドは、内部的に、IEquatable<T>.EqualsまたはObject.Equals各要素に対して呼び出します。このアプローチでは明らかに型の定義を拡張する必要がありますが、その結果はシリアル化を含む一般的なソリューションよりも高速です。

編集:これは、3レベルの入れ子を使用した不自然な例です。

値型の場合、通常はその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>そのようなシナリオのための可変型の実装に頼らないようになりました。同等性には同一性同等という2つの概念があります。メモリ表現レベルでは、これらは一般に「参照の等価性」と「値の等価性」として区別されます(等価比較を参照)。ただし、同じ区別がドメインレベルでも適用できます。Personクラスに、PersonId実際の人とは異なる固有のプロパティがあるとします。同じであるPersonIdAge値が異なる2つのオブジェクトは、等しいか異なると見なされますか?上記の答えは、1つが等価の後であることを想定しています。ただし、IEquality<T>このような実装がIDを提供することを前提とする、コレクションなどのインターフェース。たとえば、にデータを入力する場合HashSet<T>、通常、TryGetValue(T,T)呼び出しは引数のIDのみを共有する既存の要素を返すことが期待されますが、必ずしも内容が完全に同じである同等の要素ではありません。この概念は、次の注記によって実施されますGetHashCode

一般に、可変参照型の場合は、次の場合にGetHashCode()のみオーバーライドする必要があります。

  • 変更できないフィールドからハッシュコードを計算できます。または
  • オブジェクトがハッシュコードに依存するコレクションに含まれている間、可変オブジェクトのハッシュコードが変更されないようにすることができます。

RIAサービスを介してこのオブジェクトを取得します...これらのオブジェクトにIEquatable <Foo>を使用して、WPFクライアントで取得できますか?
開発者

1
クラスが自動生成されるということですか?私は、RIA Servicesを使用していないが、私は、任意の生成されたクラスのように宣言されることを前提としますpartial-その場合には、はい、あなたは彼らの実装ができEqualsた自動生成からの参照フィールド/プロパティを手動で追加パーシャルクラス宣言を通じて方法を1。
ダグラス

「Address address」が実際に「Address [] adresses」である場合、それはどのように実装されますか?
guiomie 2012年

2
あなたは、LINQを呼び出すことができEnumerable.SequenceEqualアレイ上の方法を:this.Addresses.SequenceEqual(other.Addresses)。クラスがインターフェースを実装してAddress.Equalsいる場合、これは、対応するアドレスの各ペアに対してメソッドを内部的に呼び出します。AddressIEquatable<Address>
ダグラス

2
開発者がチェックする可能性のある別の比較カテゴリは、「WorksLike」です。私にとって、これは、2つのインスタンスが異なるプロパティ値を持っている場合でも、プログラムは2つのインスタンスの処理から同じ結果を生成することを意味します。
John Kurtz 2018

95

両方のオブジェクトをシリアル化し、結果の文字列を比較します


1
なぜあるのかわかりません。シリアル化は通常、最適化されたプロセスであり、どのような場合でもすべてのプロパティの値にアクセスする必要があります。
JoelFan

5
莫大な費用がかかります。データストリームを生成し、文字列を追加してから、文字列の等価性をテストしています。それだけで桁違いです。言うまでもなく、シリアライゼーションはデフォルトでリフレクションを使用します。
ジェロームハルトム

2
データストリームは大したことではありません、なぜ文字列を追加する必要があるのか​​わかりません...文字列の等価性のテストはそこにある最も最適化された操作の1つです...リフレクションでポイントがあるかもしれません...しかしシリアル化全体が他の方法よりも「桁違い」に悪くなることはありません。パフォーマンスの問題が疑われる場合は、ベンチマークを実行する必要があります...私はこの方法でパフォーマンスの問題を経験していません
JoelFan

12
これは+1、値ベースの等値比較をこの方法で行うことを考えたことがないからです。それは素晴らしくてシンプルです。このコードでいくつかのベンチマークを見ることはきちんとしているでしょう。
トーマス

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がスローされるのを回避できます。Equals()// ReobjがRedundantJumpStatementを一度無効にした場合(objValue == anotherValue)が続く場合。// null参照ガードelse if(!objValue.Equals(anotherValue))Fail(expected、actual);
マークコンウェイ

3
DeepCompare単にCompareEx再帰的に呼び出す代わりに使用する理由はありますか?
apostolov 2017年

3
これにより、構造全体が不必要に比較される場合があります。で置き換えるresultreturn false、より効率的になります。
Tim Sylvester

24

IEquatableを実装したくない場合は、常にReflectionを使用してすべてのプロパティを比較できます。-プロパティが値タイプの場合は、それらを比較する-参照タイプの場合は、関数を再帰的に呼び出して「内部」プロパティを比較する。

私はパフォーマンスについてではなく、単純さについて考えています。ただし、オブジェクトの正確な設計によって異なります。オブジェクトの形状によっては複雑になる可能性があります(たとえば、プロパティ間に循環依存がある場合)。ただし、次のようないくつかの解決策があります。

別のオプションは、たとえばJSON.NETを使用してオブジェクトをテキストとしてシリアル化し、シリアル化の結果を比較することです。(JSON.NETは、プロパティ間の循環依存関係を処理できます)。

あなたが最速でそれを実装する最速の方法を意味するのか、それとも高速に実行するコードを意味するのかはわかりません。最適化する必要があるかどうかを知る前に最適化しないでください。早期の最適化はすべての悪の根源です


1
IEquatable<T>実装が時期尚早な最適化のケースとして認められるとは、私はほとんど考えていません。反射は大幅に遅くなります。Equalsカスタム値タイプのデフォルトの実装ではリフレクションを使用します。Microsoft自身は、パフォーマンスを優先してオーバーライドすることを推奨しています。「Equals特定の型のメソッドをオーバーライドして、メソッドのパフォーマンスを向上させ、型の等価性の概念をより厳密に表す」。
ダグラス

1
それは彼がequalsメソッドを実行する回数に依存します:1、10、100、100、100万?それは大きな違いを生むでしょう。何も実装せずに一般的なソリューションを使用できれば、貴重な時間を節約できます。遅すぎる場合は、IEquatableを実装する必要があります(おそらくキャッシュ可能またはインテリジェントなGetHashCodeを作成しようとしています)Reflectionの速度に関する限り、私はそれが遅いことに同意する必要があります...つまり、PropertyInfosタイプなどを再利用するかどうか)。
JotaBe 2012年

@ Worthy7あります。プロジェクトの内容をご覧ください。テストは例によって文書化する良い方法です。しかし、それよりも優れていれば、それを探すと、.chmヘルプファイルが見つかります。したがって、このプロジェクトには、ほとんどのプロジェクトよりも優れたドキュメントがあります。
JotaBe 2017年

申し訳ありませんが、「wiki」タブを完全に逃しました。私は、Readmeで物事を書くすべての人に慣れています。
Worthy7

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などのmishmashスクリプトに移行するのには、本当に十分な理由があります。
John Alexiou

5

文字通り同じオブジェクトを参照していないと思います

Object1 == Object2

あなたは2つの間のメモリ比較を行うことを考えているかもしれません

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

しかし、それはc#の実際のコードではありません:)。すべてのデータはおそらくヒープ上に作成されるため、メモリは連続しておらず、2つのオブジェクトの同等性を不可知な方法で比較することはできません。カスタムの方法で、一度に1つずつ各値を比較する必要があります。

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
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 2017年

1

これを行う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

複雑なオブジェクトにディクショナリが含まれている場合、.netシリアライザーがそれをシリアル化できるとは思いません。Jsonシリアライザーはできます。
ashlar64
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.