#region CPL License /* Nuclex Framework Copyright (C) 2002-2013 Nuclex Development Labs This library is free software; you can redistribute it and/or modify it under the terms of the IBM Common Public License as published by the IBM Corporation; either version 1.0 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the IBM Common Public License for more details. You should have received a copy of the IBM Common Public License along with this library */ #endregion using System; using System.Reflection; using System.Runtime.Serialization; namespace Nuclex.Support.Cloning { /// Clones objects using reflection /// /// /// This type of cloning is a lot faster than cloning by serialization and /// incurs no set-up cost, but requires cloned types to provide a default /// constructor in order to work. /// /// public class ReflectionCloner : ICloneFactory { /// /// Creates a shallow clone of the specified object, reusing any referenced objects /// /// Type of the object that will be cloned /// Object that will be cloned /// A shallow clone of the provided object public static TCloned ShallowFieldClone(TCloned objectToClone) { Type originalType = objectToClone.GetType(); if(originalType.IsPrimitive || (originalType == typeof(string))) { return objectToClone; // Being value types, primitives are copied by default } else if(originalType.IsArray) { return (TCloned)shallowCloneArray(objectToClone); } else if(originalType.IsValueType) { return objectToClone; // Value types can be copied directly } else { return (TCloned)shallowCloneComplexFieldBased(objectToClone); } } /// /// Creates a shallow clone of the specified object, reusing any referenced objects /// /// Type of the object that will be cloned /// Object that will be cloned /// A shallow clone of the provided object public static TCloned ShallowPropertyClone(TCloned objectToClone) { Type originalType = objectToClone.GetType(); if(originalType.IsPrimitive || (originalType == typeof(string))) { return objectToClone; // Being value types, primitives are copied by default } else if(originalType.IsArray) { return (TCloned)shallowCloneArray(objectToClone); } else if(originalType.IsValueType) { return (TCloned)shallowCloneComplexPropertyBased(objectToClone); } else { return (TCloned)shallowCloneComplexPropertyBased(objectToClone); } } /// /// Creates a deep clone of the specified object, also creating clones of all /// child objects being referenced /// /// Type of the object that will be cloned /// Object that will be cloned /// A deep clone of the provided object public static TCloned DeepFieldClone(TCloned objectToClone) { object objectToCloneAsObject = objectToClone; if(objectToClone == null) { return default(TCloned); } else { return (TCloned)deepCloneSingleFieldBased(objectToCloneAsObject); } } /// /// Creates a deep clone of the specified object, also creating clones of all /// child objects being referenced /// /// Type of the object that will be cloned /// Object that will be cloned /// A deep clone of the provided object public static TCloned DeepPropertyClone(TCloned objectToClone) { object objectToCloneAsObject = objectToClone; if(objectToClone == null) { return default(TCloned); } else { return (TCloned)deepCloneSinglePropertyBased(objectToCloneAsObject); } } /// /// Creates a shallow clone of the specified object, reusing any referenced objects /// /// Type of the object that will be cloned /// Object that will be cloned /// A shallow clone of the provided object TCloned ICloneFactory.ShallowFieldClone(TCloned objectToClone) { if(typeof(TCloned).IsClass || typeof(TCloned).IsArray) { if(ReferenceEquals(objectToClone, null)) { return default(TCloned); } } return ReflectionCloner.ShallowFieldClone(objectToClone); } /// /// Creates a shallow clone of the specified object, reusing any referenced objects /// /// Type of the object that will be cloned /// Object that will be cloned /// A shallow clone of the provided object TCloned ICloneFactory.ShallowPropertyClone(TCloned objectToClone) { if(typeof(TCloned).IsClass || typeof(TCloned).IsArray) { if(ReferenceEquals(objectToClone, null)) { return default(TCloned); } } return ReflectionCloner.ShallowPropertyClone(objectToClone); } /// /// Creates a deep clone of the specified object, also creating clones of all /// child objects being referenced /// /// Type of the object that will be cloned /// Object that will be cloned /// A deep clone of the provided object TCloned ICloneFactory.DeepFieldClone(TCloned objectToClone) { return ReflectionCloner.DeepFieldClone(objectToClone); } /// /// Creates a deep clone of the specified object, also creating clones of all /// child objects being referenced /// /// Type of the object that will be cloned /// Object that will be cloned /// A deep clone of the provided object TCloned ICloneFactory.DeepPropertyClone(TCloned objectToClone) { return ReflectionCloner.DeepPropertyClone(objectToClone); } /// Clones a complex type using field-based value transfer /// Original instance that will be cloned /// A clone of the original instance private static object shallowCloneComplexFieldBased(object original) { Type originalType = original.GetType(); #if (XBOX360 || WINDOWS_PHONE) object clone = Activator.CreateInstance(originalType); #else object clone = FormatterServices.GetUninitializedObject(originalType); #endif FieldInfo[] fieldInfos = originalType.GetFieldInfosIncludingBaseClasses( BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ); for(int index = 0; index < fieldInfos.Length; ++index) { FieldInfo fieldInfo = fieldInfos[index]; object originalValue = fieldInfo.GetValue(original); if(originalValue != null) { // Everything's just directly assigned in a shallow clone fieldInfo.SetValue(clone, originalValue); } } return clone; } /// Clones a complex type using property-based value transfer /// Original instance that will be cloned /// A clone of the original instance private static object shallowCloneComplexPropertyBased(object original) { Type originalType = original.GetType(); object clone = Activator.CreateInstance(originalType); PropertyInfo[] propertyInfos = originalType.GetProperties( BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy ); for(int index = 0; index < propertyInfos.Length; ++index) { PropertyInfo propertyInfo = propertyInfos[index]; if(propertyInfo.CanRead && propertyInfo.CanWrite) { Type propertyType = propertyInfo.PropertyType; object originalValue = propertyInfo.GetValue(original, null); if(originalValue != null) { if(propertyType.IsPrimitive || (propertyType == typeof(string))) { // Primitive types can be assigned directly propertyInfo.SetValue(clone, originalValue, null); } else if(propertyType.IsValueType) { // Value types are seen as part of the original type and are thus recursed into propertyInfo.SetValue(clone, shallowCloneComplexPropertyBased(originalValue), null); } else if(propertyType.IsArray) { // Arrays are assigned directly in a shallow clone propertyInfo.SetValue(clone, originalValue, null); } else { // Complex types are directly assigned without creating a copy propertyInfo.SetValue(clone, originalValue, null); } } } } return clone; } /// Clones an array using field-based value transfer /// Original array that will be cloned /// A clone of the original array private static object shallowCloneArray(object original) { return ((Array)original).Clone(); } /// Copies a single object using field-based value transfer /// Original object that will be cloned /// A clone of the original object private static object deepCloneSingleFieldBased(object original) { Type originalType = original.GetType(); if(originalType.IsPrimitive || (originalType == typeof(string))) { return original; // Creates another box, does not reference boxed primitive } else if(originalType.IsArray) { return deepCloneArrayFieldBased((Array)original, originalType.GetElementType()); } else { return deepCloneComplexFieldBased(original); } } /// Clones a complex type using field-based value transfer /// Original instance that will be cloned /// A clone of the original instance private static object deepCloneComplexFieldBased(object original) { Type originalType = original.GetType(); #if (XBOX360 || WINDOWS_PHONE) object clone = Activator.CreateInstance(originalType); #else object clone = FormatterServices.GetUninitializedObject(originalType); #endif FieldInfo[] fieldInfos = originalType.GetFieldInfosIncludingBaseClasses( BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ); for(int index = 0; index < fieldInfos.Length; ++index) { FieldInfo fieldInfo = fieldInfos[index]; Type fieldType = fieldInfo.FieldType; object originalValue = fieldInfo.GetValue(original); if(originalValue != null) { // Primitive types can be assigned directly if(fieldType.IsPrimitive || (fieldType == typeof(string))) { fieldInfo.SetValue(clone, originalValue); } else if(fieldType.IsArray) { // Arrays need to be cloned element-by-element fieldInfo.SetValue( clone, deepCloneArrayFieldBased((Array)originalValue, fieldType.GetElementType()) ); } else { // Complex types need to be cloned member-by-member fieldInfo.SetValue(clone, deepCloneSingleFieldBased(originalValue)); } } } return clone; } /// Clones an array using field-based value transfer /// Original array that will be cloned /// Type of elements the original array contains /// A clone of the original array private static object deepCloneArrayFieldBased(Array original, Type elementType) { if(elementType.IsPrimitive || (elementType == typeof(string))) { return original.Clone(); } int dimensionCount = original.Rank; // Find out the length of each of the array's dimensions, also calculate how // many elements there are in the array in total. var lengths = new int[dimensionCount]; int totalElementCount = 0; for(int index = 0; index < dimensionCount; ++index) { lengths[index] = original.GetLength(index); if(index == 0) { totalElementCount = lengths[index]; } else { totalElementCount *= lengths[index]; } } // Knowing the number of dimensions and the length of each dimension, we can // create another array of the exact same sizes. Array clone = Array.CreateInstance(elementType, lengths); // If this is a one-dimensional array (most common type), do an optimized copy // directly specifying the indices if(dimensionCount == 1) { // Clone each element of the array directly for(int index = 0; index < totalElementCount; ++index) { object originalElement = original.GetValue(index); if(originalElement != null) { clone.SetValue(deepCloneSingleFieldBased(originalElement), index); } } } else { // Otherwise use the generic code for multi-dimensional arrays var indices = new int[dimensionCount]; for(int index = 0; index < totalElementCount; ++index) { // Determine the index for each of the array's dimensions int elementIndex = index; for(int dimensionIndex = dimensionCount - 1; dimensionIndex >= 0; --dimensionIndex) { indices[dimensionIndex] = elementIndex % lengths[dimensionIndex]; elementIndex /= lengths[dimensionIndex]; } // Clone the current array element object originalElement = original.GetValue(indices); if(originalElement != null) { clone.SetValue(deepCloneSingleFieldBased(originalElement), indices); } } } return clone; } /// Copies a single object using property-based value transfer /// Original object that will be cloned /// A clone of the original object private static object deepCloneSinglePropertyBased(object original) { Type originalType = original.GetType(); if(originalType.IsPrimitive || (originalType == typeof(string))) { return original; // Creates another box, does not reference boxed primitive } else if(originalType.IsArray) { return deepCloneArrayPropertyBased((Array)original, originalType.GetElementType()); } else { return deepCloneComplexPropertyBased(original); } } /// Clones a complex type using property-based value transfer /// Original instance that will be cloned /// A clone of the original instance private static object deepCloneComplexPropertyBased(object original) { Type originalType = original.GetType(); object clone = Activator.CreateInstance(originalType); PropertyInfo[] propertyInfos = originalType.GetProperties( BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy ); for(int index = 0; index < propertyInfos.Length; ++index) { PropertyInfo propertyInfo = propertyInfos[index]; if(propertyInfo.CanRead && propertyInfo.CanWrite) { Type propertyType = propertyInfo.PropertyType; object originalValue = propertyInfo.GetValue(original, null); if(originalValue != null) { if(propertyType.IsPrimitive || (propertyType == typeof(string))) { // Primitive types can be assigned directly propertyInfo.SetValue(clone, originalValue, null); } else if(propertyType.IsArray) { // Arrays need to be cloned element-by-element propertyInfo.SetValue( clone, deepCloneArrayPropertyBased((Array)originalValue, propertyType.GetElementType()), null ); } else { // Complex types need to be cloned member-by-member propertyInfo.SetValue(clone, deepCloneSinglePropertyBased(originalValue), null); } } } } return clone; } /// Clones an array using property-based value transfer /// Original array that will be cloned /// Type of elements the original array contains /// A clone of the original array private static object deepCloneArrayPropertyBased(Array original, Type elementType) { if(elementType.IsPrimitive || (elementType == typeof(string))) { return original.Clone(); } int dimensionCount = original.Rank; // Find out the length of each of the array's dimensions, also calculate how // many elements there are in the array in total. var lengths = new int[dimensionCount]; int totalElementCount = 0; for(int index = 0; index < dimensionCount; ++index) { lengths[index] = original.GetLength(index); if(index == 0) { totalElementCount = lengths[index]; } else { totalElementCount *= lengths[index]; } } // Knowing the number of dimensions and the length of each dimension, we can // create another array of the exact same sizes. Array clone = Array.CreateInstance(elementType, lengths); // If this is a one-dimensional array (most common type), do an optimized copy // directly specifying the indices if(dimensionCount == 1) { // Clone each element of the array directly for(int index = 0; index < totalElementCount; ++index) { object originalElement = original.GetValue(index); if(originalElement != null) { clone.SetValue(deepCloneSinglePropertyBased(originalElement), index); } } } else { // Otherwise use the generic code for multi-dimensional arrays var indices = new int[dimensionCount]; for(int index = 0; index < totalElementCount; ++index) { // Determine the index for each of the array's dimensions int elementIndex = index; for(int dimensionIndex = dimensionCount - 1; dimensionIndex >= 0; --dimensionIndex) { indices[dimensionIndex] = elementIndex % lengths[dimensionIndex]; elementIndex /= lengths[dimensionIndex]; } // Clone the current array element object originalElement = original.GetValue(indices); if(originalElement != null) { clone.SetValue(deepCloneSinglePropertyBased(originalElement), indices); } } } return clone; } } } // namespace Nuclex.Support.Cloning