/// <summary> /// Add difference and perform a callback /// </summary> private void AddDifference(Difference difference) { Differences.Add(difference); DifferenceCallback(difference); }
/// <summary> /// Compare two objects /// </summary> /// <param name="object1">The first object to compare</param> /// <param name="object2">The second object to compare</param> /// <param name="breadCrumb">Where we are in the object hiearchy</param> private void Compare(object object1, object object2, string breadCrumb) { //If both null return true if (object1 == null && object2 == null) return; //Check if one of them is null if (object1 == null) { Difference difference = new Difference { ExpectedName = ExpectedName, ActualName = ActualName, PropertyName = breadCrumb, Object1Value = "(null)", Object2Value = NiceString(object2), Object1 = null, Object2 = new WeakReference(object2) }; AddDifference(difference); return; } if (object2 == null) { Difference difference = new Difference { ExpectedName = ExpectedName, ActualName = ActualName, PropertyName = breadCrumb, Object1Value = NiceString(object1), Object2Value = "(null)", Object1 = new WeakReference(object1), Object2 = null }; AddDifference(difference); return; } Type t1 = object1.GetType(); Type t2 = object2.GetType(); //Objects must be the same type if (t1 != t2 && !IgnoreObjectTypes) { Difference difference = new Difference { ExpectedName = ExpectedName, ActualName = ActualName, PropertyName = breadCrumb, Object1Value = t1.FullName, Object2Value = t2.FullName, ChildPropertyName = "GetType()", MessagePrefix = "Different Types", Object1 = new WeakReference(object1), Object2 = new WeakReference(object2) }; AddDifference(difference); return; } if (IsUseCustomTypeComparer(t1)) { CompareWithCustomComparer(object1, object2, breadCrumb); } else if (IsTypeOfType(t1)) { CompareType(object1, object2, breadCrumb); } else if (IsIpEndPoint(t1)) { CompareIpEndPoint(object1, object2, breadCrumb); } #if !SILVERLIGHT else if (IsDataset(t1)) { CompareDataset(object1, object2, breadCrumb); } else if (IsDataTable(t1)) { CompareDataTable(object1, object2, breadCrumb); } else if (IsDataRow(t1)) { CompareDataRow(object1, object2, breadCrumb); } #endif else if (IsIList(t1)) //This will do arrays, multi-dimensional arrays and generic lists { CompareIList(object1, object2, breadCrumb); } else if (IsHashSet(t1)) { CompareHashSet(object1,object2,breadCrumb); } else if (IsIDictionary(t1)) { CompareIDictionary(object1, object2, breadCrumb); } else if (IsEnum(t1)) { CompareEnum(object1, object2, breadCrumb); } else if (IsPointer(t1)) { ComparePointer(object1, object2, breadCrumb); } else if (IsUri(t1)) { CompareUri(object1, object2, breadCrumb); } else if (IsSimpleType(t1)) { CompareSimpleType(object1, object2, breadCrumb); } else if (IsClass(t1)) { CompareClass(object1, object2, breadCrumb); } else if (IsTimespan(t1)) { CompareTimespan(object1, object2, breadCrumb); } else if (IsStruct(t1)) { CompareStruct(object1, object2, breadCrumb); } else { throw new NotSupportedException("Cannot compare object of type " + t1.Name); } }
/// <summary> /// Compare all tables and all rows in all tables /// </summary> /// <param name="object1"></param> /// <param name="object2"></param> /// <param name="breadCrumb"></param> private void CompareDataset(object object1, object object2, string breadCrumb) { DataSet dataSet1 = object1 as DataSet; DataSet dataSet2 = object2 as DataSet; //This should never happen, null check happens one level up if (dataSet1 == null || dataSet2 == null) return; //There must be the same amount of tables in the dataset if (dataSet1.Tables.Count != dataSet2.Tables.Count) { Difference difference = new Difference { ExpectedName = ExpectedName, ActualName = ActualName, PropertyName = breadCrumb, Object1Value = dataSet1.Tables.Count.ToString(CultureInfo.InvariantCulture), Object2Value = dataSet2.Tables.Count.ToString(CultureInfo.InvariantCulture), ChildPropertyName = "Tables.Count", Object1 = new WeakReference(object1), Object2 = new WeakReference(object2) }; AddDifference(difference); if (Differences.Count >= MaxDifferences) return; } for (int i = 0; i < Math.Min(dataSet1.Tables.Count,dataSet2.Tables.Count); i++) { string currentBreadCrumb = AddBreadCrumb(breadCrumb, "Tables", string.Empty, dataSet1.Tables[i].TableName); CompareDataTable(dataSet1.Tables[i], dataSet2.Tables[i], currentBreadCrumb); if (Differences.Count >= MaxDifferences) return; } }
/// <summary> /// Compare all rows in a data table /// </summary> /// <param name="object1"></param> /// <param name="object2"></param> /// <param name="breadCrumb"></param> private void CompareDataTable(object object1, object object2, string breadCrumb) { DataTable dataTable1 = object1 as DataTable; DataTable dataTable2 = object2 as DataTable; //This should never happen, null check happens one level up if (dataTable1 == null || dataTable2 == null) return; //Only compare specific table names if (ElementsToInclude.Count > 0 && !ElementsToInclude.Contains(dataTable1.TableName)) return; //If we should ignore it, skip it if (ElementsToInclude.Count == 0 && ElementsToIgnore.Contains(dataTable1.TableName)) return; //There must be the same amount of rows in the datatable if (dataTable1.Rows.Count != dataTable2.Rows.Count) { Difference difference = new Difference { ExpectedName = ExpectedName, ActualName = ActualName, PropertyName = breadCrumb, Object1Value = dataTable1.Rows.Count.ToString(CultureInfo.InvariantCulture), Object2Value = dataTable2.Rows.Count.ToString(CultureInfo.InvariantCulture), ChildPropertyName = "Rows.Count", Object1 = new WeakReference(object1), Object2 = new WeakReference(object2) }; AddDifference(difference); if (Differences.Count >= MaxDifferences) return; } //There must be the same amount of columns in the datatable if (dataTable1.Columns.Count != dataTable2.Columns.Count) { Difference difference = new Difference { ExpectedName = ExpectedName, ActualName = ActualName, PropertyName = breadCrumb, Object1Value = dataTable1.Columns.Count.ToString(CultureInfo.InvariantCulture), Object2Value = dataTable2.Columns.Count.ToString(CultureInfo.InvariantCulture), ChildPropertyName = "Columns.Count", Object1 = new WeakReference(object1), Object2 = new WeakReference(object2) }; AddDifference(difference); if (Differences.Count >= MaxDifferences) return; } for (int i = 0; i < Math.Min(dataTable1.Rows.Count,dataTable2.Rows.Count); i++) { string currentBreadCrumb = AddBreadCrumb(breadCrumb, "Rows", string.Empty, i); CompareDataRow(dataTable1.Rows[i], dataTable2.Rows[i], currentBreadCrumb); if (Differences.Count >= MaxDifferences) return; } }
/// <summary> /// Compare an object of type URI /// </summary> /// <param name="object1"></param> /// <param name="object2"></param> /// <param name="breadCrumb"></param> private void CompareUri(object object1, object object2, string breadCrumb) { Uri uri1 = object1 as Uri; Uri uri2 = object2 as Uri; //This should never happen, null check happens one level up if (uri1 == null || uri2 == null) return; if (uri1.OriginalString != uri2.OriginalString) { Difference difference = new Difference { ExpectedName = ExpectedName, ActualName = ActualName, PropertyName = breadCrumb, Object1Value = NiceString(uri1.OriginalString), Object2Value = NiceString(uri2.OriginalString), ChildPropertyName = "OriginalString", Object1 = new WeakReference(object1), Object2 = new WeakReference(object2) }; AddDifference(difference); } }
/// <summary> /// Compare all columns in a data row /// </summary> /// <param name="object1"></param> /// <param name="object2"></param> /// <param name="breadCrumb"></param> private void CompareDataRow(object object1, object object2, string breadCrumb) { DataRow dataRow1 = object1 as DataRow; DataRow dataRow2 = object2 as DataRow; //This should never happen, null check happens one level up if (dataRow1 == null || dataRow2 == null) return; for (int i = 0; i < dataRow1.Table.Columns.Count; i++) { //Only compare specific column names if (ElementsToInclude.Count > 0 && !ElementsToInclude.Contains(dataRow1.Table.Columns[i].ColumnName)) continue; //If we should ignore it, skip it if (ElementsToInclude.Count == 0 && ElementsToIgnore.Contains(dataRow1.Table.Columns[i].ColumnName)) continue; //If we should ignore read only, skip it if (!CompareReadOnly && dataRow1.Table.Columns[i].ReadOnly) continue; //Both are null if (dataRow1.IsNull(i) && dataRow2.IsNull(i)) continue; string currentBreadCrumb = AddBreadCrumb(breadCrumb, string.Empty, string.Empty, dataRow1.Table.Columns[i].ColumnName); //Check if one of them is null if (dataRow1.IsNull(i)) { Difference difference = new Difference { ExpectedName = ExpectedName, ActualName = ActualName, PropertyName = currentBreadCrumb, Object1Value = "(null)", Object2Value = NiceString(object2), Object1 = new WeakReference(object1), Object2 = new WeakReference(object2) }; AddDifference(difference); return; } if (dataRow2.IsNull(i)) { Difference difference = new Difference { ExpectedName = ExpectedName, ActualName = ActualName, PropertyName = currentBreadCrumb, Object1Value = NiceString(object1), Object2Value = "(null)", Object1 = new WeakReference(object1), Object2 = new WeakReference(object2) }; AddDifference(difference); return; } //Check if one of them is deleted if (dataRow1.RowState == DataRowState.Deleted ^ dataRow2.RowState == DataRowState.Deleted) { Difference difference = new Difference { ExpectedName = ExpectedName, ActualName = ActualName, PropertyName = currentBreadCrumb, Object1Value = dataRow1.RowState.ToString(), Object2Value = dataRow2.RowState.ToString(), Object1 = new WeakReference(object1), Object2 = new WeakReference(object2) }; AddDifference(difference); return; } Compare(dataRow1[i], dataRow2[i], currentBreadCrumb); if (Differences.Count >= MaxDifferences) return; } }
/// <summary> /// Compare a timespan struct /// </summary> /// <param name="object1"></param> /// <param name="object2"></param> /// <param name="breadCrumb"></param> private void CompareTimespan(object object1, object object2, string breadCrumb) { if (((TimeSpan)object1).Ticks != ((TimeSpan)object2).Ticks) { Difference difference = new Difference { ExpectedName = ExpectedName, ActualName = ActualName, PropertyName = breadCrumb, Object1Value = ((TimeSpan)object1).Ticks.ToString(CultureInfo.InvariantCulture), Object2Value = ((TimeSpan)object1).Ticks.ToString(CultureInfo.InvariantCulture), ChildPropertyName = "Ticks", Object1 = new WeakReference(object1), Object2 = new WeakReference(object2) }; AddDifference(difference); } }
/// <summary> /// Compare an array or something that implements IList /// </summary> /// <param name="object1"></param> /// <param name="object2"></param> /// <param name="breadCrumb"></param> private void CompareIList(object object1, object object2, string breadCrumb) { IList ilist1 = object1 as IList; IList ilist2 = object2 as IList; //This should never happen, null check happens one level up if (ilist1 == null || ilist2 == null) return; try { AddParent(object1.GetHashCode()); AddParent(object2.GetHashCode()); //Objects must be the same length if (ilist1.Count != ilist2.Count) { Difference difference = new Difference { ExpectedName = ExpectedName, ActualName = ActualName, PropertyName = breadCrumb, Object1Value = ilist1.Count.ToString(CultureInfo.InvariantCulture), Object2Value = ilist2.Count.ToString(CultureInfo.InvariantCulture), ChildPropertyName = "Count", Object1 = new WeakReference(ilist1), Object2 = new WeakReference(ilist2) }; AddDifference(difference); if (Differences.Count >= MaxDifferences) return; } IEnumerator enumerator1 = ilist1.GetEnumerator(); IEnumerator enumerator2 = ilist2.GetEnumerator(); int count = 0; if (IgnoreCollectionOrder) { CompareEnumeratorIgnoreOrder(enumerator1, enumerator2, breadCrumb); } else { while (enumerator1.MoveNext() && enumerator2.MoveNext()) { string currentBreadCrumb = AddBreadCrumb(breadCrumb, string.Empty, string.Empty, count); Compare(enumerator1.Current, enumerator2.Current, currentBreadCrumb); if (Differences.Count >= MaxDifferences) return; count++; } } } finally { RemoveParent(object1.GetHashCode()); RemoveParent(object2.GetHashCode()); } }
/// <summary> /// Compare a pointer struct /// </summary> /// <param name="object1"></param> /// <param name="object2"></param> /// <param name="breadCrumb"></param> private void ComparePointer(object object1, object object2, string breadCrumb) { if ((object1 is IntPtr && object2 is IntPtr && ((IntPtr)object1) != ((IntPtr)object2)) || (object1 is UIntPtr && object2 is UIntPtr && ((UIntPtr)object1) != ((UIntPtr)object2))) { Difference difference = new Difference { ExpectedName = ExpectedName, ActualName = ActualName, PropertyName = breadCrumb, Object1 = new WeakReference(object1), Object2 = new WeakReference(object2) }; AddDifference(difference); } }
/// <summary> /// Compare a simple type /// </summary> /// <param name="object1"></param> /// <param name="object2"></param> /// <param name="breadCrumb"></param> private void CompareSimpleType(object object1, object object2, string breadCrumb) { //This should never happen, null check happens one level up if (object2 == null || object1 == null) return; IComparable valOne = object1 as IComparable; if (valOne == null) throw new Exception("Expected value does not implement IComparable"); if (valOne.CompareTo(object2) != 0) { Difference difference = new Difference { ExpectedName = ExpectedName, ActualName = ActualName, PropertyName = breadCrumb, Object1Value = object1.ToString(), Object2Value = object2.ToString(), Object1 = new WeakReference(object1), Object2 = new WeakReference(object2) }; AddDifference(difference); } }
private void CompareIpEndPoint(object object1, object object2, string breadCrumb) { IPEndPoint ipEndPoint1 = object1 as IPEndPoint; IPEndPoint ipEndPoint2 = object2 as IPEndPoint; //Null check happens above if (ipEndPoint1 == null || ipEndPoint2 == null) return; if (ipEndPoint1.Port != ipEndPoint2.Port) { Difference difference = new Difference { ExpectedName = ExpectedName, ActualName = ActualName, PropertyName = breadCrumb, Object1Value = ipEndPoint1.Port.ToString(CultureInfo.InvariantCulture), Object2Value = ipEndPoint2.Port.ToString(CultureInfo.InvariantCulture), ChildPropertyName = "Port", Object1 = new WeakReference(ipEndPoint1), Object2 = new WeakReference(ipEndPoint2) }; AddDifference(difference); } if (Differences.Count >= MaxDifferences) return; if (ipEndPoint1.Address.ToString() != ipEndPoint2.Address.ToString()) { Difference difference = new Difference { ExpectedName = ExpectedName, ActualName = ActualName, PropertyName = breadCrumb, Object1Value = ipEndPoint1.Address.ToString(), Object2Value = ipEndPoint2.Address.ToString(), ChildPropertyName = "Address", Object1 = new WeakReference(ipEndPoint1), Object2 = new WeakReference(ipEndPoint2) }; AddDifference(difference); } }
private void CompareIndexerIgnoreOrder(PropertyInfo info, object object1, object object2, string breadCrumb, int indexerCount1, int indexerCount2) { int i = 0; //if object2 holds more items than object1 we need to track, and then show diffs of the remaining //items var needTrackingOfObject2 = indexerCount2 > indexerCount1; var foundObject2Items = new List<int>(); while (i < indexerCount1) { string currentCrumb = breadCrumb; object objectValue1 = info.GetValue(object1, new object[] { i }); var found = false; // expect order, but don't assume it. this improves performance // where the collections are ordered in sync (wittingly, or un-), but // requires some fancy logic to loop back from 0 to i var currentLimiter = indexerCount2; var j = (i >= indexerCount2) ? 0 : i; var loopedBack = false; bool needLoopBack = j > 0; while (j < currentLimiter) { var clone = Clone(); clone.Differences = new List<Difference>(); currentCrumb = AddBreadCrumb(breadCrumb, "Current", string.Empty, i); object objectValue2 = info.GetValue(object2, new object[] { j }); if (HasMatchingSpecEntry(objectValue1)) { if (clone.MatchObjects(objectValue1, objectValue2, ref currentCrumb)) { found = true; Compare(objectValue1, objectValue2, currentCrumb); foundObject2Items.Add(j); break; } clone.Compare(objectValue1, objectValue2, currentCrumb); } else { clone.Compare(objectValue1, objectValue2, currentCrumb); } if (clone.Differences.Count == 0) { found = true; if (needTrackingOfObject2) { foundObject2Items.Add(j); } break; } j++; if (needLoopBack && j == currentLimiter && !loopedBack) { j = 0; currentLimiter = i; loopedBack = true; } } if (!found) { Difference difference = new Difference { ExpectedName = ExpectedName, ActualName = ActualName, PropertyName = currentCrumb, Object1Value = NiceString(objectValue1), Object2Value = "(null)", ChildPropertyName = "Item", Object1 = new WeakReference(objectValue1), Object2 = null }; AddDifference(difference); } if (Differences.Count >= MaxDifferences) return; i++; } if (needTrackingOfObject2) { for (int j = 0; j < indexerCount2; j++) { if (!foundObject2Items.Contains(j)) { object objectValue2 = info.GetValue(object2, new object[] { j }); var matchingEntry = GetMatchingSpecEntry(objectValue2); var currentCrumb = AddBreadCrumb(breadCrumb, string.Empty, (null == matchingEntry)? string.Empty: GetMatchPhrase(objectValue2), j); Compare(null, objectValue2, currentCrumb); if (Differences.Count >= MaxDifferences) return; } } } }
/// <summary> /// Compare an integer indexer /// </summary> /// <param name="info"></param> /// <param name="object1"></param> /// <param name="object2"></param> /// <param name="breadCrumb"></param> private void CompareIndexer(PropertyInfo info, object object1, object object2, string breadCrumb) { string currentCrumb; int indexerCount1 = (int)info.ReflectedType.GetProperty("Count").GetGetMethod().Invoke(object1, new object[] { }); int indexerCount2 = (int)info.ReflectedType.GetProperty("Count").GetGetMethod().Invoke(object2, new object[] { }); //Indexers must be the same length if (indexerCount1 != indexerCount2) { currentCrumb = AddBreadCrumb(breadCrumb, info.Name, string.Empty, -1); Difference difference = new Difference { ExpectedName = ExpectedName, ActualName = ActualName, PropertyName = currentCrumb, Object1Value = indexerCount1.ToString(CultureInfo.InvariantCulture), Object2Value = indexerCount2.ToString(CultureInfo.InvariantCulture), ChildPropertyName = "Count", Object1 = new WeakReference(object1), Object2 = new WeakReference(object2) }; AddDifference(difference); if (Differences.Count >= MaxDifferences) return; } if (IgnoreCollectionOrder) { CompareIndexerIgnoreOrder(info, object1, object2, breadCrumb, indexerCount1, indexerCount2); } else { // Run on indexer for (int i = 0; i < indexerCount1; i++) { currentCrumb = AddBreadCrumb(breadCrumb, info.Name, string.Empty, i); object objectValue1 = info.GetValue(object1, new object[] { i }); object objectValue2 = null; if (i < indexerCount2) objectValue2 = info.GetValue(object2, new object[] { i }); Compare(objectValue1, objectValue2, currentCrumb); if (Differences.Count >= MaxDifferences) return; } if (indexerCount1 < indexerCount2) { for (int j = indexerCount1; j < indexerCount2; j++) { currentCrumb = AddBreadCrumb(breadCrumb, info.Name, string.Empty, j); object objectValue2 = info.GetValue(object2, new object[] { j }); Compare(null, objectValue2, currentCrumb); if (Differences.Count >= MaxDifferences) return; } } } }
/// <summary> /// Compare an enumeration /// </summary> /// <param name="object1"></param> /// <param name="object2"></param> /// <param name="breadCrumb"></param> private void CompareEnum(object object1, object object2, string breadCrumb) { if (object1.ToString() != object2.ToString()) { Difference difference = new Difference { ExpectedName = ExpectedName, ActualName = ActualName, PropertyName = breadCrumb, Object1Value = object1.ToString(), Object2Value = object2.ToString(), Object1 = new WeakReference(object1), Object2 = new WeakReference(object2) }; AddDifference(difference); } }
/// <summary> /// Compare an object of type Type (Runtime type) /// </summary> /// <param name="object1"></param> /// <param name="object2"></param> /// <param name="breadCrumb"></param> private void CompareType(object object1, object object2, string breadCrumb) { Type t1 = (Type)object1; Type t2 = (Type)object2; if (t1.FullName != t2.FullName) { Difference difference = new Difference { ExpectedName = ExpectedName, ActualName = ActualName, PropertyName = breadCrumb, Object1Value = t1.FullName, Object2Value = t2.FullName, ChildPropertyName = "FullName", Object1 = new WeakReference(object1), Object2 = new WeakReference(object2) }; AddDifference(difference); } }
private void CompareEnumeratorIgnoreOrder(IEnumerator enumerator1, IEnumerator enumerator2, string breadCrumb) { var loopedBack = false; var needLoopBack = false; var enumerator1Index = -1; var enumerator2Index = 0; int? limit = null; //if object2 holds more items than object1 we need to track, and then show diffs of the remaining //items var needTrackingOfObject2 = IsBigger(enumerator1, enumerator2); var foundObject2Items = new List<int>(); while (enumerator1.MoveNext()) { enumerator1Index++; var found = false; // expect order, but don't assume it. this improves performance // where the collections are ordered in sync (wittingly, or un-), but // the logic is a bit different than the above index compare, var movedNext = enumerator2.MoveNext(); if (!movedNext) { enumerator2.Reset(); movedNext = enumerator2.MoveNext(); enumerator2Index = 0; } string currentBreadCrumb = breadCrumb; while (movedNext) { var clone = Clone(); clone.Differences = new List<Difference>(); currentBreadCrumb = AddBreadCrumb(breadCrumb, "Current", string.Empty, enumerator1Index); if (HasMatchingSpecEntry(enumerator1.Current)) { if (clone.MatchObjects(enumerator1.Current, enumerator2.Current, ref currentBreadCrumb)) { found = true; Compare(enumerator1.Current, enumerator2.Current, currentBreadCrumb); foundObject2Items.Add(enumerator2Index); break; } clone.Compare(enumerator1.Current, enumerator2.Current, currentBreadCrumb); } else { clone.Compare(enumerator1.Current, enumerator2.Current, currentBreadCrumb); } if (clone.Differences.Count == 0) { found = true; if (needTrackingOfObject2) { foundObject2Items.Add(enumerator2Index); } break; } movedNext = enumerator2.MoveNext(); enumerator2Index++; if (enumerator2Index == limit) break; if (!movedNext && needLoopBack && !loopedBack) { enumerator2.Reset(); limit = enumerator2Index; enumerator2Index = 0; movedNext = enumerator2.MoveNext(); loopedBack = true; } } if (!found) { Difference difference = new Difference { ExpectedName = ExpectedName, ActualName = ActualName, PropertyName = currentBreadCrumb, Object1Value = NiceString(enumerator1.Current), Object2Value = "(null)", ChildPropertyName = "Item", Object1 = new WeakReference(enumerator1), Object2 = null }; AddDifference(difference); } if (Differences.Count >= MaxDifferences) return; needLoopBack = true; } enumerator2.Reset(); if (needTrackingOfObject2) { var j = 0; while (enumerator2.MoveNext()) { if (!foundObject2Items.Contains(j)) { object objectValue2 = enumerator2.Current; var matchingEntry = GetMatchingSpecEntry(objectValue2); var currentCrumb = AddBreadCrumb(breadCrumb, string.Empty, (null == matchingEntry) ? string.Empty : GetMatchPhrase(objectValue2), j); Compare(null, objectValue2, currentCrumb); if (Differences.Count >= MaxDifferences) return; } j++; } } }
/// <summary> /// Compare a HashSet /// </summary> /// <param name="object1"></param> /// <param name="object2"></param> /// <param name="breadCrumb"></param> private void CompareHashSet(object object1, object object2, string breadCrumb) { try { AddParent(object1.GetHashCode()); AddParent(object2.GetHashCode()); Type t1 = object1.GetType(); //Get count by reflection since we can't cast it to HashSet<> int hashSet1Count = (int) GetPropertyValue(t1, object1, "Count"); int hashSet2Count = (int) GetPropertyValue(t1, object2, "Count"); //Objects must be the same length if (hashSet1Count != hashSet2Count) { Difference difference = new Difference { ExpectedName = ExpectedName, ActualName = ActualName, PropertyName = breadCrumb, Object1Value = hashSet1Count.ToString(CultureInfo.InvariantCulture), Object2Value = hashSet2Count.ToString(CultureInfo.InvariantCulture), ChildPropertyName = "Count", Object1 = new WeakReference(hashSet1Count), Object2 = new WeakReference(hashSet2Count) }; AddDifference(difference); if (Differences.Count >= MaxDifferences) return; } //Get enumerators by reflection MethodInfo methodInfo = GetMethod(t1, "GetEnumerator"); IEnumerator enumerator1 = (IEnumerator) methodInfo.Invoke(object1, null); IEnumerator enumerator2 = (IEnumerator) methodInfo.Invoke(object2, null); int count = 0; if (IgnoreCollectionOrder) { CompareEnumeratorIgnoreOrder(enumerator1, enumerator2, breadCrumb); } else { while (enumerator1.MoveNext() && enumerator2.MoveNext()) { string currentBreadCrumb = AddBreadCrumb(breadCrumb, string.Empty, string.Empty, count); Compare(enumerator1.Current, enumerator2.Current, currentBreadCrumb); if (Differences.Count >= MaxDifferences) return; count++; } } } finally { RemoveParent(object1.GetHashCode()); RemoveParent(object2.GetHashCode()); } }