C#でのオブジェクトプロパティの比較[終了]


111

これは、他の多くのクラスから継承されたクラスのメソッドとして思いついたものです。これは、同じタイプのオブジェクトのプロパティを簡単に比較できるという考え方です。

これでうまくいきますが、コードの品質を向上させるために、精査のために捨てると思いました。どのようにそれはより良い/より効率的/などになることができますか?

/// <summary>
/// Compare property values (as strings)
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public bool PropertiesEqual(object comparisonObject)
{

    Type sourceType = this.GetType();
    Type destinationType = comparisonObject.GetType();

    if (sourceType == destinationType)
    {
        PropertyInfo[] sourceProperties = sourceType.GetProperties();
        foreach (PropertyInfo pi in sourceProperties)
        {
            if ((sourceType.GetProperty(pi.Name).GetValue(this, null) == null && destinationType.GetProperty(pi.Name).GetValue(comparisonObject, null) == null))
            {
                // if both are null, don't try to compare  (throws exception)
            }
            else if (!(sourceType.GetProperty(pi.Name).GetValue(this, null).ToString() == destinationType.GetProperty(pi.Name).GetValue(comparisonObject, null).ToString()))
            {
                // only need one property to be different to fail Equals.
                return false;
            }
        }
    }
    else
    {
        throw new ArgumentException("Comparison object must be of the same type.","comparisonObject");
    }

    return true;
}


3
ところで、あなたはこのSEサイトを知っていますか:codereview.stackexchange.com
wip

オブジェクト比較ライブラリはいくつかあります:CompareNETObjectsDeepEqualAutoCompareZCompare ...
nawfal

...としているそのうちのいくつかの一般的な等値比較の実装のトン:MemberwiseEqualityComparerEQUSemanticComparisonEqualityComparerDeepEqualityComparer平等Equals.Fody。後者のグループは、達成できることに関して範囲と柔軟性が制限される可能性があります。
nawfal 2017

回答:


160

単体テストの作成に役立つような何かを行うコードのスニペットを探していました。これが私が最終的に使用したものです。

public static bool PublicInstancePropertiesEqual<T>(T self, T to, params string[] ignore) where T : class 
  {
     if (self != null && to != null)
     {
        Type type = typeof(T);
        List<string> ignoreList = new List<string>(ignore);
        foreach (System.Reflection.PropertyInfo pi in type.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance))
        {
           if (!ignoreList.Contains(pi.Name))
           {
              object selfValue = type.GetProperty(pi.Name).GetValue(self, null);
              object toValue = type.GetProperty(pi.Name).GetValue(to, null);

              if (selfValue != toValue && (selfValue == null || !selfValue.Equals(toValue)))
              {
                 return false;
              }
           }
        }
        return true;
     }
     return self == to;
  }

編集:

上記と同じコードですが、LINQメソッドとExtensionメソッドを使用します。

public static bool PublicInstancePropertiesEqual<T>(this T self, T to, params string[] ignore) where T : class
{
    if (self != null && to != null)
    {
        var type = typeof(T);
        var ignoreList = new List<string>(ignore);
        var unequalProperties =
            from pi in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
            where !ignoreList.Contains(pi.Name) && pi.GetUnderlyingType().IsSimpleType() && pi.GetIndexParameters().Length == 0
            let selfValue = type.GetProperty(pi.Name).GetValue(self, null)
            let toValue = type.GetProperty(pi.Name).GetValue(to, null)
            where selfValue != toValue && (selfValue == null || !selfValue.Equals(toValue))
            select selfValue;
        return !unequalProperties.Any();
    }
    return self == to;
}

public static class TypeExtensions
   {
      /// <summary>
      /// Determine whether a type is simple (String, Decimal, DateTime, etc) 
      /// or complex (i.e. custom class with public properties and methods).
      /// </summary>
      /// <see cref="http://stackoverflow.com/questions/2442534/how-to-test-if-type-is-primitive"/>
      public static bool IsSimpleType(
         this Type type)
      {
         return
            type.IsValueType ||
            type.IsPrimitive ||
            new[]
            {
               typeof(String),
               typeof(Decimal),
               typeof(DateTime),
               typeof(DateTimeOffset),
               typeof(TimeSpan),
               typeof(Guid)
            }.Contains(type) ||
            (Convert.GetTypeCode(type) != TypeCode.Object);
      }

      public static Type GetUnderlyingType(this MemberInfo member)
      {
         switch (member.MemberType)
         {
            case MemberTypes.Event:
               return ((EventInfo)member).EventHandlerType;
            case MemberTypes.Field:
               return ((FieldInfo)member).FieldType;
            case MemberTypes.Method:
               return ((MethodInfo)member).ReturnType;
            case MemberTypes.Property:
               return ((PropertyInfo)member).PropertyType;
            default:
               throw new ArgumentException
               (
                  "Input MemberInfo must be if type EventInfo, FieldInfo, MethodInfo, or PropertyInfo"
               );
         }
      }
   }

ビッグT-かなり古いですが、テストと単純な比較の両方に大きな目的を確実に果たします。感謝+1
ジムトーラン

1
これは良いことですが、もっと複雑なオブジェクトでは機能しないことがわかりました。たとえば、いくつかの文字列を持つオブジェクトがあります(それらは正常に比較されます)が、このオブジェクトには別のオブジェクトのリストも含まれているため、正しく比較されないため、何らかの方法で再帰する必要があります。
Ryan Thomas

1
他の場合に例外をスローするインデックス付きプロパティを除外する必要があるため、最初の基準にさらに2つの基準を追加する必要がありました。このエラーの基準は次のとおりです:pi.GetIndexParameters()。Length ==0。そして、@ RyanThomasによって示された問題を解決するための2番目の基準は、pi.GetUnderlyingType()。IsSimpleType()です。ご覧のとおり、IsSimpleTypeは、クラスTypeには存在しない拡張機能です。答えを変更して、これらすべての条件と拡張子を追加しました。
Samuel

64

更新: Compare-Net-Objectsの最新バージョンはGitHub にあり、NuGetパッケージチュートリアルがあります。それはのように呼び出すことができます

//This is the comparison class
CompareLogic compareLogic = new CompareLogic();

ComparisonResult result = compareLogic.Compare(person1, person2);

//These will be different, write out the differences
if (!result.AreEqual)
    Console.WriteLine(result.DifferencesString);

または、構成を変更する必要がある場合は、

CompareLogic basicComparison = new CompareLogic() 
{ Config = new ComparisonConfig()
   { MaxDifferences = propertyCount 
     //add other configurations
   }
};

構成可能なパラメーターの完全なリストは、ComparisonConfig.csにあります

元の答え:

あなたのコードに見られる制限:

  • 最大のものは、深いオブジェクト比較を行わないことです。

  • プロパティがリストである場合、またはリストが要素として含まれている場合(これはnレベルになる可能性があります)、要素ごとの比較は行いません。

  • 一部のタイプのプロパティを比較してはならないことは考慮されていません(たとえば、PagedCollectionViewクラスのプロパティのように、フィルタリングの目的で使用されるFuncプロパティ)。

  • 実際にどのプロパティが異なっていたかを追跡しません(そのため、アサーションで表示できます)。

プロパティごとの詳細な比較を行うユニットテストの目的でいくつかの解決策を探していましたが、最終的にはhttp://comparenetobjects.codeplex.comを使用しました。

これは、次のように単純に使用できるクラスが1つしかない無料のライブラリです。

var compareObjects = new CompareObjects()
{
    CompareChildren = true, //this turns deep compare one, otherwise it's shallow
    CompareFields = false,
    CompareReadOnly = true,
    ComparePrivateFields = false,
    ComparePrivateProperties = false,
    CompareProperties = true,
    MaxDifferences = 1,
    ElementsToIgnore = new List<string>() { "Filter" }
};

Assert.IsTrue(
    compareObjects.Compare(objectA, objectB), 
    compareObjects.DifferencesString
);

また、Silverlight用に簡単に再コンパイルできます。1つのクラスをSilverlightプロジェクトにコピーし、プライベートメンバーの比較のように、Silverlightでは利用できない比較のための1行または2行のコードを削除するだけです。


2
Liviu、クラスがSilverlightと互換性がないというコメントに気付きました。SilverlightおよびWindows Phone 7と互換性があるように変更しました。最新のものを入手してください。comparenetobjects.codeplex.com/SourceControl/list/changesetsで変更セット74131を参照
Greg Finzer

これは有望に見えます。試してみる
DJ Burb

素晴らしい例をありがとう!また、IgnoreObjectTypesタイプが異なる場合にも設定が役立つ場合があります。
セルゲイブルノフ2013年

バージョン2.0には、Silverlight 5 +、Windows Phone 8 +、WinRT 8 +、Xamarin IOSおよびXamarin Droidと互換性のあるポータブルクラスライブラリバージョンがあります
Greg Finzer '22

DifferencesStringCompareObjectsクラスで廃止されました。:しかし、今、あなたの代わりに比較結果からそれを得ることができますvar r = compareObjects.Compare(objectA, objectB); Assert.IsTrue(r.AreEqual, r.DifferencesString);
マリアーノDesanze

6

より適切な説明については、 Override Object#Equals()のパターンに従うのが最善だと思います
:Bill Wagnerの効果的なC# -項目9を読んでください

public override Equals(object obOther)
{
  if (null == obOther)
    return false;
  if (object.ReferenceEquals(this, obOther)
    return true;
  if (this.GetType() != obOther.GetType())
    return false;
  # private method to compare members.
  return CompareMembers(this, obOther as ThisClass);
}
  • また、等しいかどうかをチェックするメソッドでは、trueまたはfalseを返す必要があります。それらが等しいか等しくないかのどちらかです。例外をスローする代わりに、falseを返します。
  • Object#Equalsをオーバーライドすることを検討します。
  • これを考慮したに違いありませんが、Reflectionを使用してプロパティを比較するのはおそらく遅いです(これを裏付ける数値はありません)。これはC#のvalueType#Equalsの既定の動作であり、値の型のEqualsをオーバーライドし、パフォーマンスを向上させるためにメンバーごとに比較することをお勧めします。(以前は、カスタムプロパティオブジェクトのコレクションがあるので、これをすぐに読みました...悪いです。)

2011年12月更新:

  • もちろん、型にすでにプロダクションEquals()がある場合は、別のアプローチが必要です。
  • これを使用してテスト目的でのみ不変データ構造を比較する場合、Equalsを本番クラスに追加しないでください(Equals実装をチェーンすることでテストをホース化したり、プロダクションに必要なEquals実装の作成を妨げたりする可能性があります) 。

継承される基本クラスにこれを実装しようとしているため、.Equals()のオーバーライドで問題が発生しました...これが実行されるクラスのキーがわからないため、私はできませんGetHasCode()の適切なオーバーライドを実装します(Equals()をオーバーライドするときに必要です)。
nailitdown 2009

要件は、objA.Equals(objB)の場合、objA.GetHashCode()== objB.GetHashCode()の場合です。GetHashCodeはクラスの変更可能な状態/データに依存するべきではありません...クラスのキーによってあなたが意味したものを取得できませんでした。解決できるもののようです。基本タイプには「キー」がありませんか?
岐阜県

6

パフォーマンスが重要でない場合は、シリアル化して結果を比較できます。

var serializer = new XmlSerializer(typeof(TheObjectType));
StringWriter serialized1 = new StringWriter(), serialized2 = new StringWriter();
serializer.Serialize(serialized1, obj1);
serializer.Serialize(serialized2, obj2);
bool areEqual = serialized1.ToString() == serialized2.ToString();

4
しばらく前にこれを試しましたが、シリアル化できないオブジェクトがいくつあるのか不思議に思います...
Offler

5

Big Tの答えはかなり良かったと思いますが、深い比較が欠けていたので、少し調整しました。

using System.Collections.Generic;
using System.Reflection;

/// <summary>Comparison class.</summary>
public static class Compare
{
    /// <summary>Compare the public instance properties. Uses deep comparison.</summary>
    /// <param name="self">The reference object.</param>
    /// <param name="to">The object to compare.</param>
    /// <param name="ignore">Ignore property with name.</param>
    /// <typeparam name="T">Type of objects.</typeparam>
    /// <returns><see cref="bool">True</see> if both objects are equal, else <see cref="bool">false</see>.</returns>
    public static bool PublicInstancePropertiesEqual<T>(T self, T to, params string[] ignore) where T : class
    {
        if (self != null && to != null)
        {
            var type = self.GetType();
            var ignoreList = new List<string>(ignore);
            foreach (var pi in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
            {
                if (ignoreList.Contains(pi.Name))
                {
                    continue;
                }

                var selfValue = type.GetProperty(pi.Name).GetValue(self, null);
                var toValue = type.GetProperty(pi.Name).GetValue(to, null);

                if (pi.PropertyType.IsClass && !pi.PropertyType.Module.ScopeName.Equals("CommonLanguageRuntimeLibrary"))
                {
                    // Check of "CommonLanguageRuntimeLibrary" is needed because string is also a class
                    if (PublicInstancePropertiesEqual(selfValue, toValue, ignore))
                    {
                        continue;
                    }

                    return false;
                }

                if (selfValue != toValue && (selfValue == null || !selfValue.Equals(toValue)))
                {
                    return false;
                }
            }

            return true;
        }

        return self == to;
    }
}

4

次の行をPublicInstancePropertiesEqualメソッドに追加して、コピーと貼り付けのエラーを回避します。

Assert.AreNotSame(self, to);

2

プロパティ内にあるすべてのオブジェクトの.ToString()をオーバーライドしますか?そうしないと、その2番目の比較でnullが返される可能性があります。

また、その2番目の比較では、(A!= B)と比較した!(A == B)の構成について、6ヶ月/ 2年後の読みやすさの点で私は垣間見えます。ライン自体はかなり幅広です。ワイドモニターを使用している場合は問題ありませんが、うまく印刷できない場合があります。(ニッチ)

このコードが機能するように、すべてのオブジェクトは常にプロパティを使用していますか?オブジェクトごとに異なる可能性のある内部の非プロパティデータが存在する可能性がありますが、公開されたデータはすべて同じですか?時間の経過とともに変化する可能性があるいくつかのデータを考えています。たとえば、2つの乱数ジェネレーターがたまたま同じ数に到達したが、2つの異なる情報シーケンスを生成したり、公開されないデータだけを生成したりします。プロパティインターフェイスを介して。


良い点-!= ...同意、要点 ToString()は、オブジェクトを返す.GetValueを回避するための試みでした(したがって、参照比較なので、比較は常にfalseです)。より良い方法はありますか?
nailitdown 2009

GetValueがオブジェクトを返す場合、この関数をもう一度再帰できますか?つまり、返されたオブジェクトでPropertiesEqualを呼び出しますか?
mmr 2009

1

同じタイプのオブジェクトのみを比較する場合、または継承チェーンのさらに下の部分を比較する場合は、オブジェクトではなく、パラメーターを基本タイプとして指定してください。

また、パラメーターのnullチェックも行います。

さらに、コードを読みやすくするために「var」を使用します(c#3コードの場合)

また、オブジェクトにプロパティとして参照型がある場合は、実際には値を比較しないToString()を呼び出すだけです。ToStringが上書きされていない場合は、タイプ名を文字列として返すため、誤検知が返される可能性があります。


参照型についての良い点-私の場合それは問題ではありませんが、そうする可能性は十分あります。
nailitdown 2009

1

私が最初に提案するのは、実際の比較を分割して少し読みやすくすることです(ToString()も取り出しました-必要ですか?):

else {
    object originalProperty = sourceType.GetProperty(pi.Name).GetValue(this, null);
    object comparisonProperty = destinationType.GetProperty(pi.Name).GetValue(comparisonObject, null);

    if (originalProperty != comparisonProperty)
        return false;

次の提案は、リフレクションの使用を可能な限り最小限に抑えることです-それは本当に遅いです。つまり、本当に遅い。これを行う場合は、プロパティ参照をキャッシュすることをお勧めします。私はReflection APIに詳しくないので、これが少しずれている場合は、コンパイルするように調整してください。

// elsewhere
Dictionary<object, Property[]> lookupDictionary = new Dictionary<object, Property[]>;

Property[] objectProperties = null;
if (lookupDictionary.ContainsKey(sourceType)) {
  objectProperties = lookupProperties[sourceType];
} else {
  // build array of Property references
  PropertyInfo[] sourcePropertyInfos = sourceType.GetProperties();
  Property[] sourceProperties = new Property[sourcePropertyInfos.length];
  for (int i=0; i < sourcePropertyInfos.length; i++) {
    sourceProperties[i] = sourceType.GetProperty(pi.Name);
  }
  // add to cache
  objectProperties = sourceProperties;
  lookupDictionary[object] = sourceProperties;
}

// loop through and compare against the instances

しかし、私は他のポスターに同意することを言わなければなりません。これは怠惰で非効率的なにおいがします。代わりにIComparableを実装する必要があります:-)。


IComparableだけを見ていましたが、並べ替えと順序付けのようでした。2つのオブジェクトの等価性を比較するのに本当に便利ですか?
ネイルダウン

絶対に、.Equals(object o)はthis.CompareTo(o)== 0として定義されているため、equalsはComparesTo()を使用して等しいかどうかを判断します。これは、リフレクションを使用するよりもはるかに効率的です(標準的な方法)。
tsimon 2009

CompareTo()を参照してEqualsが実装されている(または実装されている必要がある)と想定すると、誤解される可能性があります。ここで説明するようにあなたに等しいをオーバーライド検討する必要があります。stackoverflow.com/questions/104158/...を
tsimon

1

ここでは、null = nullを等しいものとして扱うように修正されています

 private bool PublicInstancePropertiesEqual<T>(T self, T to, params string[] ignore) where T : class
        {
            if (self != null && to != null)
            {
                Type type = typeof(T);
                List<string> ignoreList = new List<string>(ignore);
                foreach (PropertyInfo pi in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
                {
                    if (!ignoreList.Contains(pi.Name))
                    {
                        object selfValue = type.GetProperty(pi.Name).GetValue(self, null);
                        object toValue = type.GetProperty(pi.Name).GetValue(to, null);
                        if (selfValue != null)
                        {
                            if (!selfValue.Equals(toValue))
                                return false;
                        }
                        else if (toValue != null)
                            return false;
                    }
                }
                return true;
            }
            return self == to;
        }

深いオブジェクトグラフがある場合、変更された古いプロパティと新しいプロパティのリストを返すために上記を使用する最良の方法は何ですか?
Rod

1

私はこれをやった:

    public static string ToStringNullSafe(this object obj)
    {
        return obj != null ? obj.ToString() : String.Empty;
    }
    public static bool Compare<T>(T a, T b)
    {
        int count = a.GetType().GetProperties().Count();
        string aa, bb;
        for (int i = 0; i < count; i++)
        {
            aa = a.GetType().GetProperties()[i].GetValue(a, null).ToStringNullSafe();
            bb = b.GetType().GetProperties()[i].GetValue(b, null).ToStringNullSafe();
            if (aa != bb)
            {
                return false;
            }
        }
        return true;
    }

使用法:

    if (Compare<ObjectType>(a, b))

更新

一部のプロパティを名前で無視する場合:

    public static string ToStringNullSafe(this object obj)
    {
        return obj != null ? obj.ToString() : String.Empty;
    }
    public static bool Compare<T>(T a, T b, params string[] ignore)
    {
        int count = a.GetType().GetProperties().Count();
        string aa, bb;
        for (int i = 0; i < count; i++)
        {
            aa = a.GetType().GetProperties()[i].GetValue(a, null).ToStringNullSafe();
            bb = b.GetType().GetProperties()[i].GetValue(b, null).ToStringNullSafe();
            if (aa != bb && ignore.Where(x => x == a.GetType().GetProperties()[i].Name).Count() == 0)
            {
                return false;
            }
        }
        return true;
    }

使用法:

    if (MyFunction.Compare<ObjType>(a, b, "Id","AnotherProp"))

1

タイプごとに一度だけGetPropertiesを呼び出すことにより、コードを最適化できます。

public static string ToStringNullSafe(this object obj)
{
    return obj != null ? obj.ToString() : String.Empty;
}
public static bool Compare<T>(T a, T b, params string[] ignore)
{
    var aProps = a.GetType().GetProperties();
    var bProps = b.GetType().GetProperties();
    int count = aProps.Count();
    string aa, bb;
    for (int i = 0; i < count; i++)
    {
        aa = aProps[i].GetValue(a, null).ToStringNullSafe();
        bb = bProps[i].GetValue(b, null).ToStringNullSafe();
        if (aa != bb && ignore.Where(x => x == aProps[i].Name).Count() == 0)
        {
            return false;
        }
    }
    return true;
}

1

完全を期すために、http://www.cyotek.com/blog/comparing-the-properties-of-two-objects-via-reflectionへの参照を追加したいと思い ます ます。このページの他のほとんどの回答よりも完全なロジックがあります。

しかし、私は比較ネットは、オブジェクト好むライブラリ https://github.com/GregFinzer/Compare-Net-Objects(で言及はLiviu Trifoiさんの答え
ライブラリは、NuGetパッケージを持っているhttp://www.nuget.org/packages/ CompareNETObjectsと構成する複数のオプション。


1

オブジェクトが存在しないことを確認してください null

持つobj1obj2

if(obj1 == null )
{
   return false;
}
return obj1.Equals( obj2 );

両方がnullの場合はどうなりますか?それらは等しくありませんか?
mmr 2009

.Equalsを()を使用して、私の場合はヌルの良い点は、私はこの解決策を考え出すてきた理由である、動作するようには思えない
nailitdown

まあ、私がテストしているケースは2つのオブジェクトで、1つは新しく作成され、もう1つはセッションからのものです。2つを.Equals()と比較すると、どちらも同じプロパティ値であってもfalseが返されます
nailitdown

0

これは、オブジェクトが異なる場合でも機能します。ユーティリティクラスのメソッドをカスタマイズできます。プライベートプロパティも比較したいかもしれません...

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

class ObjectA
{
    public string PropertyA { get; set; }
    public string PropertyB { get; set; }
    public string PropertyC { get; set; }
    public DateTime PropertyD { get; set; }

    public string FieldA;
    public DateTime FieldB;
}

class ObjectB
{
    public string PropertyA { get; set; }
    public string PropertyB { get; set; }
    public string PropertyC { get; set; }
    public DateTime PropertyD { get; set; }


    public string FieldA;
    public DateTime FieldB;


}

class Program
{
    static void Main(string[] args)
    {
        // create two objects with same properties
        ObjectA a = new ObjectA() { PropertyA = "test", PropertyB = "test2", PropertyC = "test3" };
        ObjectB b = new ObjectB() { PropertyA = "test", PropertyB = "test2", PropertyC = "test3" };

        // add fields to those objects
        a.FieldA = "hello";
        b.FieldA = "Something differnt";

        if (a.ComparePropertiesTo(b))
        {
            Console.WriteLine("objects have the same properties");
        }
        else
        {
            Console.WriteLine("objects have diferent properties!");
        }


        if (a.CompareFieldsTo(b))
        {
            Console.WriteLine("objects have the same Fields");
        }
        else
        {
            Console.WriteLine("objects have diferent Fields!");
        }

        Console.Read();
    }
}

public static class Utilities
{
    public static bool ComparePropertiesTo(this Object a, Object b)
    {
        System.Reflection.PropertyInfo[] properties = a.GetType().GetProperties(); // get all the properties of object a

        foreach (var property in properties)
        {
            var propertyName = property.Name;

            var aValue = a.GetType().GetProperty(propertyName).GetValue(a, null);
            object bValue;

            try // try to get the same property from object b. maybe that property does
                // not exist! 
            {
                bValue = b.GetType().GetProperty(propertyName).GetValue(b, null);
            }
            catch
            {
                return false;
            }

            if (aValue == null && bValue == null)
                continue;

            if (aValue == null && bValue != null)
                return false;

            if (aValue != null && bValue == null)
               return false;

            // if properties do not match return false
            if (aValue.GetHashCode() != bValue.GetHashCode())
            {
                return false;
            }
        }

        return true;
    }



    public static bool CompareFieldsTo(this Object a, Object b)
    {
        System.Reflection.FieldInfo[] fields = a.GetType().GetFields(); // get all the properties of object a

        foreach (var field in fields)
        {
            var fieldName = field.Name;

            var aValue = a.GetType().GetField(fieldName).GetValue(a);

            object bValue;

            try // try to get the same property from object b. maybe that property does
            // not exist! 
            {
                bValue = b.GetType().GetField(fieldName).GetValue(b);
            }
            catch
            {
                return false;
            }

            if (aValue == null && bValue == null)
               continue;

            if (aValue == null && bValue != null)
               return false;

            if (aValue != null && bValue == null)
               return false;


            // if properties do not match return false
            if (aValue.GetHashCode() != bValue.GetHashCode())
            {
                return false;
            }
        }

        return true;
    }


}

そのコードは100%効率的ではありません。たとえば、タイプオブジェクトのプロパティが含まれている場合など、状況によっては機能しません。
Tono Nam

0

上記のLiviuの回答の更新-CompareObjects.DifferencesStringは非推奨になりました。

これは単体テストでうまく機能します:

CompareLogic compareLogic = new CompareLogic();
ComparisonResult result = compareLogic.Compare(object1, object2);
Assert.IsTrue(result.AreEqual);

1
あなたが非難を修正したのは素晴らしいことですが、私はこの答えは実際にはリヴィウの答えのコメントであるべきだと思います。特に、サンプルコード(Liviusと比較して)にはCompareLogicのパラメーター(これは重要だと思います)とアサートメッセージ(非推奨のもの)が不足しているためです。アサートがで固定することができます:Assert.IsTrue(result.AreEqual, result.DifferencesString);
マリアーノDesanze

0

このメソッドはproperties、クラスを取得し、それぞれの値を比較しますproperty。値のいずれかが異なる場合はreturn false、それ以外の場合はになりreturn trueます。

public 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 (Object1 == null || object2 == null)
        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;
}

使用法:

bool isEqual = Compare<Employee>(Object1, Object2)


0

@nawfal:sの回答を拡張するために、ユニットテストでさまざまなタイプのオブジェクトをテストして、等しいプロパティ名を比較するためにこれを使用します。私の場合、データベースエンティティとDTO。

私のテストではこのように使用しました。

Assert.IsTrue(resultDto.PublicInstancePropertiesEqual(expectedEntity));



public static bool PublicInstancePropertiesEqual<T, Z>(this T self, Z to, params string[] ignore) where T : class
{
    if (self != null && to != null)
    {
        var type = typeof(T);
        var type2 = typeof(Z);
        var ignoreList = new List<string>(ignore);
        var unequalProperties =
           from pi in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
           where !ignoreList.Contains(pi.Name)
           let selfValue = type.GetProperty(pi.Name).GetValue(self, null)
           let toValue = type2.GetProperty(pi.Name).GetValue(to, null)
           where selfValue != toValue && (selfValue == null || !selfValue.Equals(toValue))
           select selfValue;
           return !unequalProperties.Any();
    }
    return self == null && to == null;
}

0

すべてのパブリックプロパティを比較するのではなく、それらのサブセットのみを比較したい場合があるので、この場合は、ロジックを移動して、必要なプロパティのリストを抽象クラスと比較できます。

public abstract class ValueObject<T> where T : ValueObject<T>
{
    protected abstract IEnumerable<object> GetAttributesToIncludeInEqualityCheck();

    public override bool Equals(object other)
    {
        return Equals(other as T);
    }

    public bool Equals(T other)
    {
        if (other == null)
        {
            return false;
        }

        return GetAttributesToIncludeInEqualityCheck()
            .SequenceEqual(other.GetAttributesToIncludeInEqualityCheck());
    }

    public static bool operator ==(ValueObject<T> left, ValueObject<T> right)
    {
        return Equals(left, right);
    }

    public static bool operator !=(ValueObject<T> left, ValueObject<T> right)
    {
        return !(left == right);
    }

    public override int GetHashCode()
    {
        int hash = 17;
        foreach (var obj in this.GetAttributesToIncludeInEqualityCheck())
            hash = hash * 31 + (obj == null ? 0 : obj.GetHashCode());

        return hash;
    }
}

後でこの抽象クラスを使用してオブジェクトを比較します

public class Meters : ValueObject<Meters>
{
    ...

    protected decimal DistanceInMeters { get; private set; }

    ...

    protected override IEnumerable<object> GetAttributesToIncludeInEqualityCheck()
    {
        return new List<Object> { DistanceInMeters };
    }
}

0

私のソリューションは、上記のAras Aleninの回答からインスピレーションを得て、1レベルのオブジェクト比較と、比較結果のカスタムオブジェクトを追加しました。オブジェクト名でプロパティ名を取得することにも興味があります。

    public static IEnumerable<ObjectPropertyChanged> GetPublicSimplePropertiesChanged<T>(this T previous, T proposedChange,
     string[] namesOfPropertiesToBeIgnored) where T : class
    {
        return GetPublicGenericPropertiesChanged(previous, proposedChange, namesOfPropertiesToBeIgnored, true, null, null);
    }

    public static IReadOnlyList<ObjectPropertyChanged> GetPublicGenericPropertiesChanged<T>(this T previous, T proposedChange,
        string[] namesOfPropertiesToBeIgnored) where T : class
    {
        return GetPublicGenericPropertiesChanged(previous, proposedChange, namesOfPropertiesToBeIgnored, false, null, null);
    }

    /// <summary>
    /// Gets the names of the public properties which values differs between first and second objects.
    /// Considers 'simple' properties AND for complex properties without index, get the simple properties of the children objects.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="previous">The previous object.</param>
    /// <param name="proposedChange">The second object which should be the new one.</param>
    /// <param name="namesOfPropertiesToBeIgnored">The names of the properties to be ignored.</param>
    /// <param name="simpleTypeOnly">if set to <c>true</c> consider simple types only.</param>
    /// <param name="parentTypeString">The parent type string. Meant only for recursive call with simpleTypeOnly set to <c>true</c>.</param>
    /// <param name="secondType">when calling recursively, the current type of T must be clearly defined here, as T will be more generic (using base class).</param>
    /// <returns>
    /// the names of the properties
    /// </returns>
    private static IReadOnlyList<ObjectPropertyChanged> GetPublicGenericPropertiesChanged<T>(this T previous, T proposedChange,
        string[] namesOfPropertiesToBeIgnored, bool simpleTypeOnly, string parentTypeString, Type secondType) where T : class
    {
        List<ObjectPropertyChanged> propertiesChanged = new List<ObjectPropertyChanged>();

        if (previous != null && proposedChange != null)
        {
            var type = secondType == null ? typeof(T) : secondType;
            string typeStr = parentTypeString + type.Name + ".";
            var ignoreList = namesOfPropertiesToBeIgnored.CreateList();
            IEnumerable<IEnumerable<ObjectPropertyChanged>> genericPropertiesChanged =
                from pi in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
                where !ignoreList.Contains(pi.Name) && pi.GetIndexParameters().Length == 0 
                    && (!simpleTypeOnly || simpleTypeOnly && pi.PropertyType.IsSimpleType())
                let firstValue = type.GetProperty(pi.Name).GetValue(previous, null)
                let secondValue = type.GetProperty(pi.Name).GetValue(proposedChange, null)
                where firstValue != secondValue && (firstValue == null || !firstValue.Equals(secondValue))
                let subPropertiesChanged = simpleTypeOnly || pi.PropertyType.IsSimpleType()
                    ? null
                    : GetPublicGenericPropertiesChanged(firstValue, secondValue, namesOfPropertiesToBeIgnored, true, typeStr, pi.PropertyType)
                let objectPropertiesChanged = subPropertiesChanged != null && subPropertiesChanged.Count() > 0
                    ? subPropertiesChanged
                    : (new ObjectPropertyChanged(proposedChange.ToString(), typeStr + pi.Name, firstValue.ToStringOrNull(), secondValue.ToStringOrNull())).CreateList()
                select objectPropertiesChanged;

            if (genericPropertiesChanged != null)
            {   // get items from sub lists
                genericPropertiesChanged.ForEach(a => propertiesChanged.AddRange(a));
            }
        }
        return propertiesChanged;
    }

次のクラスを使用して比較結果を保存する

[System.Serializable]
public class ObjectPropertyChanged
{
    public ObjectPropertyChanged(string objectId, string propertyName, string previousValue, string changedValue)
    {
        ObjectId = objectId;
        PropertyName = propertyName;
        PreviousValue = previousValue;
        ProposedChangedValue = changedValue;
    }

    public string ObjectId { get; set; }

    public string PropertyName { get; set; }

    public string PreviousValue { get; set; }

    public string ProposedChangedValue { get; set; }
}

そして、単体テストの例:

    [TestMethod()]
    public void GetPublicGenericPropertiesChangedTest1()
    {
        // Define objects to test
        Function func1 = new Function { Id = 1, Description = "func1" };
        Function func2 = new Function { Id = 2, Description = "func2" };
        FunctionAssignment funcAss1 = new FunctionAssignment
        {
            Function = func1,
            Level = 1
        };
        FunctionAssignment funcAss2 = new FunctionAssignment
        {
            Function = func2,
            Level = 2
        };

        // Main test: read properties changed
        var propertiesChanged = Utils.GetPublicGenericPropertiesChanged(funcAss1, funcAss2, null);

        Assert.IsNotNull(propertiesChanged);
        Assert.IsTrue(propertiesChanged.Count == 3);
        Assert.IsTrue(propertiesChanged[0].PropertyName == "FunctionAssignment.Function.Description");
        Assert.IsTrue(propertiesChanged[1].PropertyName == "FunctionAssignment.Function.Id");
        Assert.IsTrue(propertiesChanged[2].PropertyName == "FunctionAssignment.Level");
    }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.