예제 #1
0
        /// <summary>
        /// Get the differences between two objects
        /// </summary>
        /// <param name="propertyName">The name of the property being compared</param>
        /// <param name="propertyType">The type of property being compared. The left property is assumed unless allowCompareDifferentObjects=true</param>
        /// <param name="typeConverter">An optional TypeConverter to treat the type as a different type</param>
        /// <param name="left">The left object to compare</param>
        /// <param name="right">The right object to compare</param>
        /// <param name="parent">The parent object</param>
        /// <param name="differences">A list of differences currently found in the tree</param>
        /// <param name="currentDepth">The current depth of the tree recursion</param>
        /// <param name="maxDepth">The maximum number of tree children to recurse</param>
        /// <param name="objectTree">A hash table containing the tree that has already been traversed, to prevent recursion loops</param>
        /// <param name="path">The current path</param>
        /// <param name="options">Specify the comparison options</param>
        /// <param name="propertiesToExcludeOrInclude">A list of property names or full path names to include/exclude. Default is <seealso cref="ComparisonOptions.ExcludeList"/>. Specify <seealso cref="ComparisonOptions.ExcludeList"/> to exclude the specified properties from the Diff or <seealso cref="ComparisonOptions.IncludeList"/> to only Diff properties contained in the list.</param>
        /// <param name="diffOptions">Specify custom diff options</param>
        /// <returns></returns>
        private List <Difference> GetDifferences(string propertyName, Type propertyType, TypeConverter typeConverter, IEnumerable <CustomAttributeData> attributes, object left, object right, object parent, List <Difference> differences, int currentDepth, int maxDepth, ObjectHashcodeMap objectTree, string path, ComparisonOptions options, ICollection <string> propertiesToExcludeOrInclude, DiffOptions diffOptions)
        {
            var propertyTypeSupport = propertyType.GetExtendedType(DefaultTypeSupportOptions);
            var isCollection        = propertyType != typeof(string) && propertyType.GetInterface(nameof(IEnumerable)) != null;

            object leftValue  = null;
            object rightValue = null;

            leftValue = left;

            if (options.BitwiseHasFlag(ComparisonOptions.AllowCompareDifferentObjects) && rightValue != null)
            {
                rightValue = GetValueForProperty(right, propertyName);
            }
            else
            {
                rightValue = right;
            }

            if (!isCollection || (isCollection && !options.BitwiseHasFlag(ComparisonOptions.TreatEmptyListAndNullTheSame)))
            {
                if (rightValue == null && leftValue != null || leftValue == null && rightValue != null)
                {
                    if (GetPropertyInclusionState(propertyName, path, options, propertiesToExcludeOrInclude, attributes, diffOptions.AttributeIgnoreList) == FilterResult.Include)
                    {
                        differences.Add(new Difference((leftValue ?? rightValue).GetType(), propertyName, path, leftValue, rightValue, typeConverter));
                    }
                    return(differences);
                }
            }

            if (leftValue == null && rightValue == null)
            {
                return(differences);
            }

            var leftValueType  = leftValue?.GetType() ?? propertyType;
            var rightValueType = rightValue?.GetType() ?? propertyType;

            if (isCollection && options.BitwiseHasFlag(ComparisonOptions.CompareCollections))
            {
                var genericArguments = propertyType.GetGenericArguments();
                var isArray          = propertyTypeSupport.IsArray;
                var elementType      = propertyTypeSupport.ElementType;
                // iterate the collection
                var aValueCollection      = (leftValue as IEnumerable);
                var aValueCollectionCount = GetCountFromEnumerable(aValueCollection);
                var bValueCollection      = (rightValue as IEnumerable);
                var bValueCollectionCount = GetCountFromEnumerable(bValueCollection);
                var bValueEnumerator      = bValueCollection?.GetEnumerator();
                if (options.BitwiseHasFlag(ComparisonOptions.TreatEmptyListAndNullTheSame) &&
                    aValueCollectionCount == 0 && bValueCollectionCount == 0)
                {
                    // skip collection equality check, they both have no elements or are null
                    return(differences);
                }

                if (aValueCollection != null)
                {
                    var leftIndex    = 0;
                    var matchTracker = new MatchTracker();
                    // compare elements must be the same order
                    foreach (var collectionItem in aValueCollection)
                    {
                        // iterate the left side
                        leftValue = collectionItem;
                        matchTracker.AddLeft(leftValue, leftIndex);
                        var hasMatch = false;
                        var hasValue = false;
                        if (options.BitwiseHasFlag(ComparisonOptions.AllowCollectionsToBeOutOfOrder))
                        {
                            bValueEnumerator?.Reset();
                        }
                        var rightIndex = 0;
                        do
                        {
                            // iterate the right side
                            ObjectHashcodeMap childObjectTree = null;
                            // we can't use the object tree here when allowing out of order collections because we will compare the same collection element multiple times.
                            if (!options.BitwiseHasFlag(ComparisonOptions.AllowCollectionsToBeOutOfOrder))
                            {
                                childObjectTree = objectTree;
                            }
                            hasValue = bValueEnumerator?.MoveNext() ?? false;
                            if (!hasValue)
                            {
                                if (leftIndex == rightIndex)
                                {
                                    // left has a value in collection, right does not. That's a difference
                                    if (GetPropertyInclusionState(propertyName, path, options, propertiesToExcludeOrInclude, attributes, diffOptions.AttributeIgnoreList) == FilterResult.Include)
                                    {
                                        differences.Add(new Difference(leftValue?.GetType() ?? elementType, propertyName, path, leftIndex, leftValue, null, typeConverter));
                                    }
                                }
                                break;
                            }

                            rightValue = bValueEnumerator?.Current;
                            matchTracker.AddRight(rightValue, rightIndex);
                            rightIndex++;
                            if (leftValue == null && rightValue == null)
                            {
                                continue;
                            }
                            if (leftValue == null && rightValue != null)
                            {
                                continue;
                            }
                            // check array element for difference
                            if (leftValue != null && !leftValue.GetType().IsValueType&& leftValue.GetType() != typeof(string))
                            {
                                var itemDifferences = RecurseProperties(leftValue, rightValue, parent, new List <Difference>(), currentDepth, maxDepth, childObjectTree, path, options, propertiesToExcludeOrInclude, diffOptions);
                                if (itemDifferences.Any() && options.BitwiseHasFlag(ComparisonOptions.AllowCollectionsToBeOutOfOrder))
                                {
                                    continue;
                                }
                                else if (itemDifferences.Any())
                                {
                                    differences.AddRange(itemDifferences);
                                    hasMatch = true;
                                }
                                else
                                {
                                    hasMatch = true;
                                }
                            }
                            else if (leftValue != null && leftValue.GetType().IsGenericType&& leftValue.GetType().GetGenericTypeDefinition() == typeof(KeyValuePair <,>))
                            {
                                // compare keys and values of a KVP
                                var leftKvpKey    = GetValueForProperty(leftValue, "Key");
                                var leftKvpValue  = GetValueForProperty(leftValue, "Value");
                                var rightKvpKey   = GetValueForProperty(rightValue, "Key");
                                var rightKvpValue = GetValueForProperty(rightValue, "Value");

                                var leftKvpKeyType   = leftKvpKey?.GetType() ?? genericArguments.First();
                                var leftKvpValueType = leftKvpValue?.GetType() ?? genericArguments.Skip(1).First();

                                // compare the key
                                if (leftKvpKey != null && !leftKvpKeyType.IsValueType && leftKvpKeyType != typeof(string))
                                {
                                    var itemDifferences = RecurseProperties(leftKvpKey, rightKvpKey, leftValue, new List <Difference>(), currentDepth, maxDepth, childObjectTree, path, options, propertiesToExcludeOrInclude, diffOptions);
                                    if (itemDifferences.Any() && options.BitwiseHasFlag(ComparisonOptions.AllowCollectionsToBeOutOfOrder))
                                    {
                                        continue;
                                    }
                                    else if (itemDifferences.Any())
                                    {
                                        if (GetPropertyInclusionState(propertyName, path, options, propertiesToExcludeOrInclude, attributes, diffOptions.AttributeIgnoreList) == FilterResult.Include)
                                        {
                                            differences.AddRange(itemDifferences);
                                        }
                                        hasMatch = true;
                                    }
                                    else
                                    {
                                        hasMatch = true;
                                    }
                                }
                                else
                                {
                                    if (!IsMatch(leftKvpKey, rightKvpKey))
                                    {
                                        if (options.BitwiseHasFlag(ComparisonOptions.AllowCollectionsToBeOutOfOrder))
                                        {
                                            continue;
                                        }
                                        if (GetPropertyInclusionState(propertyName, path, options, propertiesToExcludeOrInclude, attributes, diffOptions.AttributeIgnoreList) == FilterResult.Include)
                                        {
                                            differences.Add(new Difference(leftKvpKeyType, propertyName, path, leftIndex, leftKvpKey, rightKvpKey, typeConverter));
                                        }
                                        hasMatch = true;
                                        break;
                                    }
                                    else
                                    {
                                        hasMatch = true;
                                    }
                                }

                                // compare the value
                                if (leftKvpValue != null && !leftKvpValueType.IsValueType && leftKvpValueType != typeof(string))
                                {
                                    var itemDifferences = RecurseProperties(leftKvpValue, rightKvpValue, leftValue, new List <Difference>(), currentDepth, maxDepth, childObjectTree, path, options, propertiesToExcludeOrInclude, diffOptions);
                                    if (itemDifferences.Any() && options.BitwiseHasFlag(ComparisonOptions.AllowCollectionsToBeOutOfOrder))
                                    {
                                        continue;
                                    }
                                    else if (itemDifferences.Any())
                                    {
                                        if (GetPropertyInclusionState(propertyName, path, options, propertiesToExcludeOrInclude, attributes, diffOptions.AttributeIgnoreList) == FilterResult.Include)
                                        {
                                            differences.AddRange(itemDifferences);
                                        }
                                        hasMatch = true;
                                    }
                                    else
                                    {
                                        hasMatch = true;
                                    }
                                }
                                else
                                {
                                    if (!IsMatch(leftValue, rightValue))
                                    {
                                        if (options.BitwiseHasFlag(ComparisonOptions.AllowCollectionsToBeOutOfOrder))
                                        {
                                            continue;
                                        }
                                        if (GetPropertyInclusionState(propertyName, path, options, propertiesToExcludeOrInclude, attributes, diffOptions.AttributeIgnoreList) == FilterResult.Include)
                                        {
                                            differences.Add(new Difference(leftKvpValueType, propertyName, path, leftIndex, leftKvpValue, rightKvpValue, typeConverter));
                                        }
                                        hasMatch = true;
                                        break;
                                    }
                                    else
                                    {
                                        hasMatch = true;
                                    }
                                }
                            }
                            else
                            {
                                if (!IsMatch(leftValue, rightValue))
                                {
                                    if (options.BitwiseHasFlag(ComparisonOptions.AllowCollectionsToBeOutOfOrder))
                                    {
                                        continue;
                                    }
                                    if (GetPropertyInclusionState(propertyName, path, options, propertiesToExcludeOrInclude, attributes, diffOptions.AttributeIgnoreList) == FilterResult.Include)
                                    {
                                        differences.Add(new Difference(leftValue?.GetType() ?? elementType, propertyName, path, leftIndex, leftValue, rightValue, typeConverter));
                                    }
                                    hasMatch = true;
                                    break;
                                }
                                else
                                {
                                    hasMatch = true;
                                }
                            }
                            if (hasMatch)
                            {
                                matchTracker.MatchLeft(leftValue, leftIndex);
                                matchTracker.MatchRight(rightValue, rightIndex - 1);
                            }
                        } while (hasValue && !hasMatch);
                        leftIndex++;
                    }

                    // check which elements were not matched to anything
                    var rightUnmatched = matchTracker.GetRightUnmatched();
                    foreach (var unmatchedElement in rightUnmatched)
                    {
                        // dont add a difference if we already detected it
                        if (!differences.Where(x => x.ArrayIndex == unmatchedElement.ArrayIndex && x.RightValue == unmatchedElement.Object).Any())
                        {
                            if (GetPropertyInclusionState(propertyName, path, options, propertiesToExcludeOrInclude, attributes, diffOptions.AttributeIgnoreList) == FilterResult.Include)
                            {
                                differences.Add(new Difference(unmatchedElement.Object?.GetType() ?? elementType, propertyName, path, unmatchedElement.ArrayIndex, null, unmatchedElement.Object, typeConverter));
                            }
                        }
                    }
                    var leftUnmatched = matchTracker.GetLeftUnmatched();
                    foreach (var unmatchedElement in leftUnmatched)
                    {
                        // dont add a difference if we already detected it
                        if (!differences.Where(x => x.ArrayIndex == unmatchedElement.ArrayIndex && x.LeftValue == unmatchedElement.Object).Any())
                        {
                            if (GetPropertyInclusionState(propertyName, path, options, propertiesToExcludeOrInclude, attributes, diffOptions.AttributeIgnoreList) == FilterResult.Include)
                            {
                                differences.Add(new Difference(unmatchedElement.Object?.GetType() ?? elementType, propertyName, path, unmatchedElement.ArrayIndex, unmatchedElement.Object, null, typeConverter));
                            }
                        }
                    }

                    if (bValueCollectionCount > leftIndex)
                    {
                        // right side has extra elements
                        var rightSideExtraElements = bValueCollectionCount - leftIndex;
                        if (bValueEnumerator != null)
                        {
                            for (var i = 0; i < rightSideExtraElements; i++)
                            {
                                var hasValue = bValueEnumerator?.MoveNext() ?? false;
                                if (hasValue)
                                {
                                    if (GetPropertyInclusionState(propertyName, path, options, propertiesToExcludeOrInclude, attributes, diffOptions.AttributeIgnoreList) == FilterResult.Include)
                                    {
                                        differences.Add(new Difference(aValueCollection.GetType(), propertyName, path, leftIndex, null, bValueEnumerator.Current, typeConverter));
                                    }
                                    leftIndex++;
                                }
                            }
                        }
                    }
                    matchTracker.Dispose();
                }
            }
            else if (!leftValueType.IsValueType && leftValueType != typeof(string))
            {
                if (leftValueType != rightValueType && leftValueType.BaseType == rightValueType.BaseType)
                {
                    if (GetPropertyInclusionState(propertyName, path, options, propertiesToExcludeOrInclude, attributes, diffOptions.AttributeIgnoreList) == FilterResult.Include)
                    {
                        differences.Add(new Difference(propertyType, propertyName, path, leftValue, rightValue, typeConverter));
                    }
                }
                else
                {
                    differences = RecurseProperties(leftValue, rightValue, leftValue, differences, currentDepth, maxDepth, objectTree, path, options, propertiesToExcludeOrInclude, diffOptions);
                }
            }
            else
            {
                if (!IsMatch(leftValue, rightValue))
                {
                    if (GetPropertyInclusionState(propertyName, path, options, propertiesToExcludeOrInclude, attributes, diffOptions.AttributeIgnoreList) == FilterResult.Include)
                    {
                        differences.Add(new Difference(propertyType, propertyName, path, leftValue, rightValue, typeConverter));
                    }
                }
            }

            return(differences);
        }
예제 #2
0
        /// <summary>
        /// Get the differences between two objects
        /// </summary>
        /// <param name="propertyName">The name of the property being compared</param>
        /// <param name="propertyType">The type of property being compared. The left property is assumed unless allowCompareDifferentObjects=true</param>
        /// <param name="typeConverter">An optional TypeConverter to treat the type as a different type</param>
        /// <param name="left">The left object to compare</param>
        /// <param name="right">The right object to compare</param>
        /// <param name="parent">The parent object</param>
        /// <param name="differences">A list of differences currently found in the tree</param>
        /// <param name="currentDepth">The current depth of the tree recursion</param>
        /// <param name="maxDepth">The maximum number of tree children to recurse</param>
        /// <param name="objectTree">A hash table containing the tree that has already been traversed, to prevent recursion loops</param>
        /// <param name="options">Specify the comparison options</param>
        /// <param name="propertyList">A list of property names or full path names to ignore</param>
        /// <returns></returns>
        private List <Difference> GetDifferences(string propertyName, string showName, Type propertyType, TypeConverter typeConverter, object left, object right, object parent, List <Difference> differences, int currentDepth, int maxDepth, ObjectHashcodeMap objectTree, string path, ComparisonOptions options, List <string> propertyList)
        {
            if (GetPropertyInclusionState(propertyName, path, options, propertyList, null) == FilterResult.Exclude)
            {
                return(differences);
            }

            object leftValue  = null;
            object rightValue = null;

            leftValue = left;

            if (options.BitwiseHasFlag(ComparisonOptions.AllowCompareDifferentObjects) && rightValue != null)
            {
                rightValue = GetValueForProperty(right, propertyName);
            }
            else
            {
                rightValue = right;
            }

            if (rightValue == null && leftValue != null || leftValue == null && rightValue != null)
            {
                differences.Add(new Difference((leftValue ?? rightValue).GetType(), propertyName, path, leftValue, rightValue, typeConverter));
                return(differences);
            }

            if (leftValue == null && rightValue == null)
            {
                return(differences);
            }

            var propertyTypeSupport = propertyType.GetExtendedType(DefaultTypeSupportOptions);
            var isCollection        = propertyType != typeof(string) && propertyType.GetInterface(nameof(IEnumerable)) != null;

            if (isCollection && options.BitwiseHasFlag(ComparisonOptions.CompareCollections))
            {
                var genericArguments = propertyType.GetGenericArguments();
                var isArray          = propertyTypeSupport.IsArray;
                var elementType      = propertyTypeSupport.ElementType;
                // iterate the collection
                var aValueCollection      = (leftValue as IEnumerable);
                var bValueCollection      = (rightValue as IEnumerable);
                var bValueCollectionCount = GetCountFromEnumerable(bValueCollection);
                var bValueEnumerator      = bValueCollection?.GetEnumerator();
                if (aValueCollection != null)
                {
                    if (!options.BitwiseHasFlag(ComparisonOptions.AllowCollectionsToBeOutOfOrder))
                    {
                        var arrayIndex = 0;
                        // compare elements must be the same order
                        foreach (var collectionItem in aValueCollection)
                        {
                            var hasValue = bValueEnumerator?.MoveNext() ?? false;
                            leftValue = collectionItem;
                            if (hasValue)
                            {
                                rightValue = bValueEnumerator?.Current;
                                if (leftValue == null && rightValue == null)
                                {
                                    continue;
                                }
                                if (leftValue == null && rightValue != null)
                                {
                                    differences.Add(new Difference(rightValue?.GetType() ?? elementType, propertyName, showName, path, arrayIndex, leftValue, rightValue, typeConverter));
                                }
                                // check array element for difference
                                if (leftValue != null && !leftValue.GetType().IsValueType&& leftValue.GetType() != typeof(string))
                                {
                                    differences = RecurseProperties(leftValue, rightValue, parent, differences, currentDepth, maxDepth, objectTree, path, options, propertyList);
                                }
                                else if (leftValue != null && leftValue.GetType().IsGenericType&& leftValue.GetType().GetGenericTypeDefinition() == typeof(KeyValuePair <,>))
                                {
                                    // compare keys and values of a KVP
                                    var leftKvpKey    = GetValueForProperty(leftValue, "Key");
                                    var leftKvpValue  = GetValueForProperty(leftValue, "Value");
                                    var rightKvpKey   = GetValueForProperty(rightValue, "Key");
                                    var rightKvpValue = GetValueForProperty(rightValue, "Value");

                                    Type leftKeyType   = leftKvpKey?.GetType() ?? genericArguments.First();
                                    Type leftValueType = leftKvpValue?.GetType() ?? genericArguments.Skip(1).First();

                                    // compare the key
                                    if (leftKvpKey != null && !leftKeyType.IsValueType && leftKeyType != typeof(string))
                                    {
                                        differences = RecurseProperties(leftKvpKey, rightKvpKey, leftValue, differences, currentDepth, maxDepth, objectTree, path, options, propertyList);
                                    }
                                    else
                                    {
                                        if (!IsMatch(leftKvpKey, rightKvpKey))
                                        {
                                            differences.Add(new Difference(leftKeyType, propertyName, path, showName, arrayIndex, leftKvpKey, rightKvpKey, typeConverter));
                                        }
                                    }

                                    // compare the value
                                    if (leftKvpValue != null && !leftValueType.IsValueType && leftValueType != typeof(string))
                                    {
                                        differences = RecurseProperties(leftKvpValue, rightKvpValue, leftValue, differences, currentDepth, maxDepth, objectTree, path, options, propertyList);
                                    }
                                    else
                                    {
                                        if (!IsMatch(leftValue, rightValue))
                                        {
                                            differences.Add(new Difference(leftValueType, propertyName, path, showName, arrayIndex, leftKvpValue, rightKvpValue, typeConverter));
                                        }
                                    }
                                }
                                else
                                {
                                    if (!IsMatch(leftValue, rightValue))
                                    {
                                        differences.Add(new Difference(leftValue?.GetType() ?? elementType, propertyName, showName, path, arrayIndex, leftValue, rightValue, typeConverter));
                                    }
                                }
                            }
                            else
                            {
                                // left has a value in collection, right does not. That's a difference
                                rightValue = null;
                                differences.Add(new Difference(leftValue?.GetType() ?? elementType, propertyName, showName, path, arrayIndex, leftValue, rightValue, typeConverter));
                            }
                            arrayIndex++;
                        }
                        if (bValueCollectionCount > arrayIndex)
                        {
                            // right side has extra elements
                            var rightSideExtraElements = bValueCollectionCount - arrayIndex;
                            if (bValueEnumerator != null)
                            {
                                for (var i = 0; i < rightSideExtraElements; i++)
                                {
                                    var hasValue = bValueEnumerator?.MoveNext() ?? false;
                                    if (hasValue)
                                    {
                                        differences.Add(new Difference(aValueCollection.GetType(), propertyName, showName, path, arrayIndex, null, bValueEnumerator.Current, typeConverter));
                                        arrayIndex++;
                                    }
                                }
                            }
                        }
                    }
                    else
                    {
                        // compare elements can be of different order (use the hashcode for this)
                        var leftHashCodeEntryList  = new Dictionary <int, CollectionKey>(); // hashcode, value/count
                        var rightHashCodeEntryList = new Dictionary <int, CollectionKey>(); // hashcode, value/count
                        var arrayIndex             = 0;
                        foreach (var collectionItem in aValueCollection)
                        {
                            var hashCode = collectionItem.GetHashCode();
                            if (leftHashCodeEntryList.ContainsKey(hashCode))
                            {
                                var val = leftHashCodeEntryList[hashCode];
                                val.Matches++;
                                val.OriginalIndex = arrayIndex; // populate the highest value
                            }
                            else
                            {
                                leftHashCodeEntryList.Add(hashCode, new CollectionKey(arrayIndex, collectionItem, 1));
                            }
                            arrayIndex++;
                        }
                        arrayIndex = 0;
                        foreach (var collectionItem in bValueCollection)
                        {
                            var hashCode = collectionItem.GetHashCode();
                            if (rightHashCodeEntryList.ContainsKey(hashCode))
                            {
                                var val = rightHashCodeEntryList[hashCode];
                                val.Matches++;
                                val.OriginalIndex = arrayIndex; // populate the highest value
                            }
                            else
                            {
                                rightHashCodeEntryList.Add(hashCode, new CollectionKey(arrayIndex, collectionItem, 1));
                            }
                            arrayIndex++;
                        }
                        var orderedLeft  = leftHashCodeEntryList.OrderBy(x => x.Key);
                        var orderedRight = rightHashCodeEntryList.OrderBy(x => x.Key);
                        // compare the left collection
                        foreach (var item in orderedLeft)
                        {
                            var matchFound = orderedRight.Where(x => x.Key == item.Key).Select(x => x.Value).FirstOrDefault();
                            if (matchFound == null)
                            {
                                differences.Add(new Difference((leftValue ?? rightValue).GetType(), propertyName, showName, path, item.Value.OriginalIndex, item.Value.Value, null, typeConverter));
                            }
                            else
                            {
                                var isEqual = CompareForObjectEquality(item.Value.Value.GetExtendedType(DefaultTypeSupportOptions), item.Value.Value, matchFound.Value);
                                if (!isEqual)
                                {
                                    differences.Add(new Difference((leftValue ?? rightValue).GetType(), propertyName, showName, path, item.Value.OriginalIndex, item.Value.Value, null, typeConverter));
                                }
                                else if (matchFound.Matches != item.Value.Matches && item.Value.Matches > matchFound.Matches)
                                {
                                    // also compare that the number of instances of the value in a collection are the same
                                    differences.Add(new Difference((leftValue ?? rightValue).GetType(), propertyName, showName, path, item.Value.OriginalIndex, item.Value.Value, null, typeConverter));
                                }
                            }
                        }
                        // compare the right collection
                        foreach (var item in orderedRight)
                        {
                            var matchFound = orderedLeft.Where(x => x.Key == item.Key).Select(x => x.Value).FirstOrDefault();
                            if (matchFound == null)
                            {
                                differences.Add(new Difference((leftValue ?? rightValue).GetType(), propertyName, showName, path, item.Value.OriginalIndex, null, item.Value.Value, typeConverter));
                            }
                            else
                            {
                                var isEqual = CompareForObjectEquality(item.Value.Value.GetExtendedType(DefaultTypeSupportOptions), item.Value.Value, matchFound.Value);
                                if (!isEqual)
                                {
                                    differences.Add(new Difference((leftValue ?? rightValue).GetType(), propertyName, showName, path, item.Value.OriginalIndex, null, item.Value.Value, typeConverter));
                                }
                                else if (matchFound.Matches != item.Value.Matches && item.Value.Matches > matchFound.Matches)
                                {
                                    // also compare that the number of instances of the value in a collection are the same
                                    differences.Add(new Difference((leftValue ?? rightValue).GetType(), propertyName, showName, path, item.Value.OriginalIndex, null, item.Value.Value, typeConverter));
                                }
                            }
                        }
                    }
                }
            }
            else if (!propertyType.IsValueType && propertyType != typeof(string))
            {
                differences = RecurseProperties(leftValue, rightValue, leftValue, differences, currentDepth, maxDepth, objectTree, path, options, propertyList);
            }
            else
            {
                if (!IsMatch(leftValue, rightValue))
                {
                    differences.Add(new Difference(propertyType, propertyName, showName, path, leftValue, rightValue, typeConverter));
                }
            }

            return(differences);
        }
예제 #3
0
        /// <summary>
        /// Recurse the object's tree
        /// </summary>
        /// <param name="left">The left object to compare</param>
        /// <param name="right">The right object to compare</param>
        /// <param name="parent">The parent object</param>
        /// <param name="differences">A list of differences currently found in the tree</param>
        /// <param name="currentDepth">The current depth of the tree recursion</param>
        /// <param name="maxDepth">The maximum number of tree children to recurse</param>
        /// <param name="objectTree">A hash table containing the tree that has already been traversed, to prevent recursion loops</param>
        /// <param name="comparisonOptions">Specify the comparison options</param>
        /// <param name="diffOptions">Specify custom diff options</param>
        /// <param name="propertiesToExcludeOrInclude">A list of property names or full path names to include/exclude. Default is <seealso cref="ComparisonOptions.ExcludeList"/>. Specify <seealso cref="ComparisonOptions.ExcludeList"/> to exclude the specified properties from the Diff or <seealso cref="ComparisonOptions.IncludeList"/> to only Diff properties contained in the list.</param>
        /// <param name="path">The current path</param>
        /// <returns></returns>
        private List <Difference> RecurseProperties(object left, object right, object parent, List <Difference> differences, int currentDepth, int maxDepth, ObjectHashcodeMap objectTree, string path, ComparisonOptions comparisonOptions, ICollection <string> propertiesToExcludeOrInclude, DiffOptions diffOptions)
        {
            if (!comparisonOptions.BitwiseHasFlag(ComparisonOptions.AllowCompareDifferentObjects) &&
                left != null && right != null &&
                left?.GetType() != right?.GetType())
            {
                throw new ArgumentException("Objects Left and Right must be of the same type.");
            }

            if (left == null && right == null)
            {
                return(differences);
            }

            if (maxDepth > 0 && currentDepth >= maxDepth)
            {
                return(differences);
            }

            var type = left != null?left.GetType() : right.GetType();

            var typeSupport = type.GetExtendedType(DefaultTypeSupportOptions);

            if (typeSupport.Attributes.Any(x => diffOptions.AttributeIgnoreList.Contains(x)))
            {
                return(differences);
            }
            if (typeSupport.IsDelegate)
            {
                return(differences);
            }

            if (comparisonOptions.BitwiseHasFlag(ComparisonOptions.AllowEqualsOverride))
            {
                // if the object has a custom equality comparitor, use its output (isObjectEqual) instead of iterating the object.
                // if it does not have one specified, this method will return false
                if (CompareForObjectEquality(typeSupport, left, right, out var isObjectEqual) && isObjectEqual)
                {
                    return(differences); // no differences found, no need to continue
                }
            }

            // increment the current recursion depth
            currentDepth++;

            // construct a hashtable of objects we have already inspected (simple recursion loop preventer)
            // we use this hashcode method as it does not use any custom hashcode handlers the object might implement
            if (left != null)
            {
                if (objectTree?.Contains(left) == true)
                {
                    return(differences);
                }
                objectTree?.Add(left);
            }

            // get list of properties
            var properties = new List <ExtendedProperty>();

            if (comparisonOptions.BitwiseHasFlag(ComparisonOptions.CompareProperties))
            {
                properties.AddRange(left.GetProperties(PropertyOptions.All));
            }

            // get all fields, except for backed auto-property fields
            var fields = new List <ExtendedField>();

            if (comparisonOptions.BitwiseHasFlag(ComparisonOptions.CompareFields))
            {
                fields.AddRange(left.GetFields(FieldOptions.All));
                fields = fields.Where(x => !x.IsBackingField).ToList();
            }

            var rootPath  = path;
            var localPath = string.Empty;

            foreach (var property in properties)
            {
                localPath = $"{rootPath}.{property.Name}";
                object leftValue = null;
                try
                {
                    if (left != null)
                    {
                        leftValue = left.GetPropertyValue(property);
                    }
                }
                catch (Exception)
                {
                    // catch any exceptions accessing the property
                }
                object rightValue = null;
                try
                {
                    if (right != null)
                    {
                        rightValue = right.GetPropertyValue(property);
                    }
                }
                catch (Exception)
                {
                    // catch any exceptions accessing the property
                }
                differences = GetDifferences(property.Name, property.Type, GetTypeConverter(property), property.CustomAttributes, leftValue, rightValue, parent, differences, currentDepth, maxDepth, objectTree, localPath, comparisonOptions, propertiesToExcludeOrInclude, diffOptions);
            }
            foreach (var field in fields)
            {
                localPath = $"{rootPath}.{field.Name}";
                object leftValue = null;
                if (left != null)
                {
                    leftValue = left.GetFieldValue(field);
                }
                object rightValue = null;
                if (right != null)
                {
                    rightValue = right.GetFieldValue(field);
                }
                differences = GetDifferences(field.Name, field.Type, GetTypeConverter(field), field.CustomAttributes, leftValue, rightValue, parent, differences, currentDepth, maxDepth, objectTree, localPath, comparisonOptions, propertiesToExcludeOrInclude, diffOptions);
            }

            return(differences);
        }
예제 #4
0
        /// <summary>
        /// Recurse the object's tree
        /// </summary>
        /// <param name="left">The left object to compare</param>
        /// <param name="right">The right object to compare</param>
        /// <param name="parent">The parent object</param>
        /// <param name="differences">A list of differences currently found in the tree</param>
        /// <param name="currentDepth">The current depth of the tree recursion</param>
        /// <param name="maxDepth">The maximum number of tree children to recurse</param>
        /// <param name="objectTree">A hash table containing the tree that has already been traversed, to prevent recursion loops</param>
        /// <param name="options">Specify the comparison options</param>
        /// <param name="propertyList">A list of property names or full path names to ignore</param>
        /// <returns></returns>
        private List <Difference> RecurseProperties(object left, object right, object parent, List <Difference> differences, int currentDepth, int maxDepth, ObjectHashcodeMap objectTree, string path, ComparisonOptions options, List <string> propertyList)
        {
            if (GetPropertyInclusionState(null, path, options, propertyList, null) == FilterResult.Exclude)
            {
                return(differences);
            }

            if (!options.BitwiseHasFlag(ComparisonOptions.AllowCompareDifferentObjects) &&
                left != null && right != null &&
                left?.GetType() != right?.GetType())
            {
                throw new ArgumentException("Objects Left and Right must be of the same type.");
            }

            if (left == null && right == null)
            {
                return(differences);
            }

            if (maxDepth > 0 && currentDepth >= maxDepth)
            {
                return(differences);
            }

            var typeSupport = new ExtendedType(left != null ? left.GetType() : right.GetType(), DefaultTypeSupportOptions);

            if (typeSupport.Attributes.Any(x => _ignoreAttributes.Contains(x)))
            {
                return(differences);
            }
            if (typeSupport.IsDelegate)
            {
                return(differences);
            }

            if (options.BitwiseHasFlag(ComparisonOptions.AllowEqualsOverride))
            {
                var objectEquality = CompareForObjectEquality(typeSupport, left, right);
                if (objectEquality)
                {
                    return(differences); // no differences found, no need to continue
                }
            }

            // increment the current recursion depth
            currentDepth++;

            // construct a hashtable of objects we have already inspected (simple recursion loop preventer)
            // we use this hashcode method as it does not use any custom hashcode handlers the object might implement
            if (left != null)
            {
                if (objectTree.Contains(left))
                {
                    return(differences);
                }
                objectTree.Add(left);
            }

            // get list of properties
            var properties = new List <ExtendedProperty>();

            if (options.BitwiseHasFlag(ComparisonOptions.CompareProperties))
            {
                var pis = left.GetProperties(PropertyOptions.All);
                foreach (var pi in pis)
                {
                    if (pi.PropertyInfo.CanWrite == false || pi.PropertyInfo.CanRead == false)
                    {
                        continue;
                    }

                    var atts   = pi.PropertyInfo.GetCustomAttributes(true);
                    var canAdd = true;
                    foreach (var att in atts)
                    {
                        var name = att.GetType().Name.ToLower();
                        if (name.Contains("ignore") || name.Contains("result"))
                        {
                            canAdd = false;
                            break;
                        }
                    }
                    if (canAdd)
                    {
                        properties.Add(pi);
                    }
                }
            }

            // get all fields, except for backed auto-property fields
            var fields = new List <ExtendedField>();

            if (options.BitwiseHasFlag(ComparisonOptions.CompareFields))
            {
                var fis = left.GetFields(FieldOptions.All);
                foreach (var fi in fis)
                {
                    if (fi.IsBackingField)
                    {
                        continue;
                    }

                    var atts   = fi.FieldInfo.GetCustomAttributes(true);
                    var canAdd = true;
                    foreach (var att in atts)
                    {
                        var name = att.GetType().Name.ToLower();
                        if (name.Contains("ignore") || name.Contains("result") || name.Contains("nonserialized"))
                        {
                            canAdd = false;
                            break;
                        }
                    }
                    if (canAdd)
                    {
                        fields.Add(fi);
                    }
                }
            }

            var rootPath  = path;
            var localPath = string.Empty;

            foreach (var property in properties)
            {
                localPath = $"{rootPath}.{property.Name}";
                if (GetPropertyInclusionState(property.Name, localPath, options, propertyList, property.CustomAttributes) == FilterResult.Exclude)
                {
                    continue;
                }
                object leftValue = null;
                try {
                    if (left != null)
                    {
                        leftValue = left.GetPropertyValue(property);
                    }
                } catch (Exception) {
                    // catch any exceptions accessing the property
                }
                object rightValue = null;
                try {
                    if (right != null)
                    {
                        rightValue = right.GetPropertyValue(property);
                    }
                } catch (Exception) {
                    // catch any exceptions accessing the property
                }
                var showName = property.Name;
                var attr     = property.PropertyInfo.GetCustomAttribute <DisplayNameAttribute>(true);
                if (attr != null)
                {
                    showName = attr.DisplayName;
                }

                differences = GetDifferences(property.Name, showName, property.Type, GetTypeConverter(property), leftValue, rightValue, parent, differences, currentDepth, maxDepth, objectTree, localPath, options, propertyList);
            }
            foreach (var field in fields)
            {
                localPath = $"{rootPath}.{field.Name}";
                if (GetPropertyInclusionState(field.Name, localPath, options, propertyList, field.CustomAttributes) == FilterResult.Exclude)
                {
                    continue;
                }
                object leftValue = null;
                if (left != null)
                {
                    leftValue = left.GetFieldValue(field);
                }
                object rightValue = null;
                if (right != null)
                {
                    rightValue = right.GetFieldValue(field);
                }
                var showName = field.Name;
                var attr     = field.FieldInfo.GetCustomAttribute <DisplayNameAttribute>(true);
                if (attr != null)
                {
                    showName = attr.DisplayName;
                }
                differences = GetDifferences(field.Name, showName, field.Type, GetTypeConverter(field), leftValue, rightValue, parent, differences, currentDepth, maxDepth, objectTree, localPath, options, propertyList);
            }

            return(differences);
        }