예제 #1
0
        /// <summary>
        /// I decided to not use recursion to avoid stack overflow, what if object graph is too large?
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        private object DeepClone(object source)
        {
            var state = CloneState.NextBreadcrumb;

            var value = source;

            var breadcrumbs = new Stack <Breadcrumb>();

            Breadcrumb bcb = null;

            IChild child = null;

            Type type = null;

            // to avoid cyclic references, if objects already exists in target tree - use it
            var objectMap = new Hashtable();

            switch (state)
            {
            case CloneState.NextChild:
                if (bcb.Childs.Length <= bcb.ChildIdx)
                {
                    if (breadcrumbs.Count == 1)
                    {
                        return(bcb.TargetObject);
                    }
                    else
                    {
                        breadcrumbs.Pop();
                        var mappedValue = objectMap[bcb.SourceObject] = bcb.TargetObject;
                        bcb   = breadcrumbs.Peek();
                        child = bcb.Childs[bcb.ChildIdx];
                        child.SetValue(bcb.TargetObject, mappedValue);
                        bcb.ChildIdx++;
                        goto case CloneState.NextChild;
                    }
                }

                child = bcb.Childs[bcb.ChildIdx];
                value = child.GetValue(bcb.SourceObject);

                if (value == null || RequiresShallowClone(child, value))
                {
                    child.SetValue(bcb.TargetObject, value);
                    bcb.ChildIdx++;
                    goto case CloneState.NextChild;
                }
                else if (objectMap.ContainsKey(value))
                {
                    child.SetValue(bcb.TargetObject, objectMap[value]);
                    bcb.ChildIdx++;
                    goto case CloneState.NextChild;
                }
                else
                {
                    goto case CloneState.NextBreadcrumb;
                }

            case CloneState.NextBreadcrumb:
                type = value.GetType();

                if (type.IsArray)
                {
                    var size = ((Array)value).Length;
                    bcb = new Breadcrumb
                    {
                        Childs       = GetArrayItems(size),
                        TargetObject = Array.CreateInstance(type.GetElementType(), size),
                    };
                }
                else if (HasParameterlessConstructor(type))
                {
                    bcb = new Breadcrumb
                    {
                        TargetObject = Activator.CreateInstance(type),
                        Childs       = GetObjectChilds(type, value),
                    };
                }
                else
                {
                    // ignore
                    if (bcb != null)
                    {
                        child.SetValue(bcb.TargetObject, null);
                        bcb.ChildIdx++;
                        goto case CloneState.NextChild;
                    }
                    else
                    {
                        throw new NotSupportedException(
                                  "cloning type schould have parameterless constructor or be a one-dimensional array");
                    }
                }

                bcb.SourceObject = value;
                bcb.ChildIdx     = 0;
                objectMap.Add(value, bcb.TargetObject);
                breadcrumbs.Push(bcb);
                goto case CloneState.NextChild;
            }

            return(source);
        }