#region Copyright 2010-2014 by Roger Knapp, Licensed under the Apache License, Version 2.0 /* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #endregion using System; using System.Collections.Generic; namespace CSharpTest.Net.Cloning { /// /// A class that performs duplication of an entire object graph /// public abstract class ObjectCloner : IDisposable { readonly IDictionary _graph; /// /// Creates the cloner /// protected ObjectCloner() { _graph = new Dictionary(new ReferenceEqualityComparer()); } /// /// Disposes of the instance and it's references to objects that have been duplicated /// public void Dispose() { Clear(); } /// /// Removes all instances from the object graph /// public virtual void Clear() { _graph.Clear(); } /// /// Add or Remove instances from the object graph, by adding this.Graph[o] = o; the instance 'o' will /// not be duplicated. /// public IDictionary Graph { [System.Diagnostics.DebuggerStepThrough] get { return _graph; } } /// /// Public entry point to begin duplication of the object graph. /// public virtual T Clone(T instance) { try { return CloneObject(instance); } finally { Clear(); } } /// /// Internal duplicate an object graph /// protected T CloneObject(T instance) { object copy; if (Object.ReferenceEquals(instance, null) || instance is string || instance is DateTime || instance is DateTimeOffset || instance is TimeSpan || instance is Boolean || instance is Byte || instance is SByte || instance is Int16 || instance is UInt16 || instance is Int32 || instance is UInt32 || instance is Int64 || instance is UInt64 || instance is IntPtr || instance is Char || instance is Double || instance is Single || instance is MarshalByRefObject) return instance; if (_graph.TryGetValue(instance, out copy)) return (T)copy; Type type = instance.GetType(); if (instance is Array) return (T)(object)CloneArray((Array)(object)instance); else if (instance is Delegate) return (T)(object)CloneDelegate((Delegate)(object)instance); else return (T)CloneDefault(instance); } /// /// Provides the default behavior for duplicating an object and recording the /// duplication into the graph. /// protected abstract object CloneDefault(object inst); private Array CloneArray(Array instance) { Array copy = (Array)((ICloneable)instance).Clone(); _graph.Add(instance, copy); int[] indexes = new int[copy.Rank]; CloneArray(copy, indexes, 0); return copy; } private void CloneArray(Array copy, int[] indexes, int rank) { int stop = copy.GetUpperBound(rank); for (indexes[rank] = copy.GetLowerBound(rank); indexes[rank] <= stop; indexes[rank]++) { if ((rank + 1) < copy.Rank) CloneArray(copy, indexes, rank + 1); else copy.SetValue(this.CloneObject(copy.GetValue(indexes)), indexes); } } private Delegate CloneDelegate(Delegate method) { Delegate result = null; foreach (Delegate del in method.GetInvocationList()) { Delegate delCopy = Delegate.CreateDelegate(del.GetType(), CloneObject(del.Target), del.Method, true); result = Delegate.Combine(result, delCopy); } _graph[method] = result; return result; } } }