Beispiel #1
0
        /// <summary>
        /// Returns true if object name should be ignored
        /// </summary>
        /// <param name="name">Property or field name</param>
        /// <param name="path">Full path to object</param>
        /// <param name="options">Comparison options</param>
        /// <param name="ignorePropertiesOrPaths">List of names or paths to ignore</param>
        /// <returns></returns>
        private bool IgnoreObjectName(string name, string path, CloneOptions options, ICollection <string> ignorePropertiesOrPaths, IEnumerable <CustomAttributeData> attributes = null)
        {
            var ignoreByNameOrPath = ignorePropertiesOrPaths?.Contains(name) == true || ignorePropertiesOrPaths?.Contains(path) == true;

            if (ignoreByNameOrPath)
            {
                return(true);
            }
#if FEATURE_CUSTOM_ATTRIBUTES
            if (attributes?.Any(x => !options.BitwiseHasFlag(CloneOptions.DisableIgnoreAttributes) && (_ignoreAttributes.Contains(x.AttributeType) || _ignoreAttributes.Contains(x.AttributeType.Name))) == true)
#else
            if (attributes?.Any(x => !options.BitwiseHasFlag(CloneOptions.DisableIgnoreAttributes) && (_ignoreAttributes.Contains(x.Constructor.DeclaringType) || _ignoreAttributes.Contains(x.Constructor.DeclaringType.Name))) == true)
#endif
            { return(true); }
            return(false);
        }
Beispiel #2
0
        /// <summary>
        /// (Recursive) Recursive function that inspects an object and its properties/fields and clones it
        /// </summary>
        /// <param name="sourceObject">The object to clone</param>
        /// <param name="currentDepth">The current tree depth</param>
        /// <param name="maxDepth">The max tree depth</param>
        /// <param name="options">The cloning options</param>
        /// <param name="objectTree">The object tree to prevent cyclical references</param>
        /// <param name="path">The current path being traversed</param>
        /// <param name="ignorePropertiesOrPaths">A list of properties or paths to ignore</param>
        /// <returns></returns>
        private object InspectAndCopy(object sourceObject, int currentDepth, int maxDepth, CloneOptions options, IDictionary <int, object> objectTree, string path, ICollection <string> ignorePropertiesOrPaths)
        {
            if (IgnoreObjectName(null, path, options, ignorePropertiesOrPaths))
            {
                return(null);
            }

            if (sourceObject == null)
            {
                return(null);
            }

            // ensure we don't go too deep if specified
            if (maxDepth > 0 && currentDepth >= maxDepth)
            {
                return(null);
            }

            var typeSupport = new ExtendedType(sourceObject.GetType(), _defaultTypeSupportOptions);

            // always return the original value on value types
            if (typeSupport.IsValueType)
            {
                return(sourceObject);
            }

            // drop any objects we are ignoring by attribute
            if (typeSupport.Attributes.Any(x => _ignoreAttributes.Contains(x)) && options.BitwiseHasFlag(CloneOptions.DisableIgnoreAttributes))
            {
                return(null);
            }

            // for delegate types, copy them by reference rather than returning null
            if (typeSupport.IsDelegate)
            {
                return(sourceObject);
            }

            object newObject = null;

            // create a new empty object of the desired type
            if (typeSupport.IsArray)
            {
                var sourceArray = sourceObject as Array;
                // calculate the dimensions of the array
                var arrayRank = sourceArray.Rank;
                // get the length of each dimension
                var arrayDimensions = new List <int>();
                for (var dimension = 0; dimension < arrayRank; dimension++)
                {
                    arrayDimensions.Add(sourceArray.GetLength(dimension));
                }
                newObject = _objectFactory.CreateEmptyObject(typeSupport.Type, default(TypeRegistry), arrayDimensions.ToArray());
            }
            else if (typeSupport.Type == typeof(string))
            {
                // copy the item directly
                newObject = String.Copy((string)sourceObject);
                return(newObject);
            }
            else
            {
                newObject = _objectFactory.CreateEmptyObject(typeSupport.Type);
            }

            if (newObject == null)
            {
                return(newObject);
            }

            // 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 (sourceObject != null && !typeSupport.IsValueType)
            {
                var hashCode = System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(sourceObject);
                if (objectTree.ContainsKey(hashCode))
                {
                    return(objectTree[hashCode]);
                }

                // ensure we can refer back to the reference for this object
                objectTree.Add(hashCode, newObject);
            }

            // clone a dictionary's key/values
            if (typeSupport.IsDictionary && typeSupport.IsGeneric)
            {
                var    genericType = typeSupport.Type.GetGenericArguments().ToList();
                Type[] typeArgs    = { genericType[0], genericType[1] };

                var listType      = typeof(Dictionary <,>).MakeGenericType(typeArgs);
                var newDictionary = Activator.CreateInstance(listType) as IDictionary;
                newObject = newDictionary;
                var iDictionary = (IDictionary)sourceObject;
                var success     = false;
                var retryCount  = 0;
                while (!success && retryCount < 10)
                {
                    try
                    {
                        foreach (DictionaryEntry item in iDictionary)
                        {
                            var key   = InspectAndCopy(item.Key, currentDepth, maxDepth, options, objectTree, path, ignorePropertiesOrPaths);
                            var value = InspectAndCopy(item.Value, currentDepth, maxDepth, options, objectTree, path, ignorePropertiesOrPaths);
                            newDictionary.Add(key, value);
                        }
                        success = true;
                    }
                    catch (InvalidOperationException)
                    {
                        // if the collection was modified during enumeration, stop re-initialize and retry
                        success = false;
                        retryCount++;
                        newDictionary.Clear();
                    }
                }
                return(newObject);
            }
            else if (typeSupport.IsEnumerable && !typeSupport.IsArray)
            {
                // clone an enumerables' elements
                var addMethod = typeSupport.Type.GetMethod("Add");
                if (addMethod == null)
                {
                    addMethod = typeSupport.Type.GetMethod("Enqueue");
                }
                if (addMethod == null)
                {
                    addMethod = typeSupport.Type.GetMethod("Push");
                }
                if (addMethod == null)
                {
                    throw new TypeException($"Unsupported IEnumerable type: {typeSupport.Type.Name}");
                }
                var enumerator = (IEnumerable)sourceObject;
                var success    = false;
                var retryCount = 0;
                while (!success && retryCount < 10)
                {
                    try
                    {
                        foreach (var item in enumerator)
                        {
                            var element = InspectAndCopy(item, currentDepth, maxDepth, options, objectTree, path, ignorePropertiesOrPaths);
                            addMethod.Invoke(newObject, new[] { element });
                        }
                        success = true;
                    }
                    catch (InvalidOperationException)
                    {
                        // if the collection was modified during enumeration, stop re-initialize and retry
                        success = false;
                        retryCount++;
                        var clearMethod = typeSupport.Type.GetMethod("Clear");
                        clearMethod?.Invoke(newObject, null);
                    }
                }
                return(newObject);
            }

            // clone an arrays' elements
            if (typeSupport.IsArray)
            {
                var sourceArray     = sourceObject as Array;
                var newArray        = newObject as Array;
                var arrayRank       = newArray.Rank;
                var arrayDimensions = new List <int>();
                for (var dimension = 0; dimension < arrayRank; dimension++)
                {
                    arrayDimensions.Add(newArray.GetLength(dimension));
                }
                var flatRowIndex = 0;
                foreach (var row in sourceArray)
                {
                    var newElement = InspectAndCopy(row, currentDepth, maxDepth, options, objectTree, path, ignorePropertiesOrPaths);
                    // performance optimization, skip dimensional processing if it's a 1d array
                    if (arrayRank > 1)
                    {
                        // this is an optimized multi-dimensional array reconstruction
                        // based on the formula: indicies.Add((i / (arrayDimensions[arrayRank - 1] * arrayDimensions[arrayRank - 2] * arrayDimensions[arrayRank - 3] * arrayDimensions[arrayRank - 4] * arrayDimensions[arrayRank - 5])) % arrayDimensions[arrayRank - 6]);
                        var indicies = new List <int>();
                        for (var r = 1; r <= arrayRank; r++)
                        {
                            var multi = 1;
                            for (var p = 1; p < r; p++)
                            {
                                multi *= arrayDimensions[arrayRank - p];
                            }
                            var b = (flatRowIndex / multi) % arrayDimensions[arrayRank - r];
                            indicies.Add(b);
                        }
                        indicies.Reverse();
                        // set element of multi-dimensional array
                        newArray.SetValue(newElement, indicies.ToArray());
                    }
                    else
                    {
                        // set element of 1d array
                        newArray.SetValue(newElement, flatRowIndex);
                    }
                    flatRowIndex++;
                }
                return(newArray);
            }

            var fields = sourceObject.GetFields(FieldOptions.AllWritable);

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

            // clone and recurse fields
            if (newObject != null)
            {
                foreach (var field in fields)
                {
                    localPath = $"{rootPath}.{field.Name}";
                    if (IgnoreObjectName(field.Name, localPath, options, ignorePropertiesOrPaths, field.CustomAttributes))
                    {
                        continue;
                    }
                    // also check the property for ignore, if this is a auto-backing property
                    if (field.BackedProperty != null && IgnoreObjectName(field.BackedProperty.Name, $"{rootPath}.{field.BackedPropertyName}", options, ignorePropertiesOrPaths, field.BackedProperty.CustomAttributes))
                    {
                        continue;
                    }
                    var fieldTypeSupport = new ExtendedType(field.Type, _defaultTypeSupportOptions);
                    var fieldValue       = sourceObject.GetFieldValue(field);
                    if (fieldTypeSupport.IsValueType || fieldTypeSupport.IsImmutable)
                    {
                        newObject.SetFieldValue(field, fieldValue);
                    }
                    else if (fieldValue != null)
                    {
                        var clonedFieldValue = InspectAndCopy(fieldValue, currentDepth, maxDepth, options, objectTree, localPath, ignorePropertiesOrPaths);
                        newObject.SetFieldValue(field, clonedFieldValue);
                    }
                }
            }

            return(newObject);
        }