#region Apache License 2.0 /* Nuclex .NET Framework Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs 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 // Apache License 2.0 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(); object clone = FormatterServices.GetUninitializedObject(originalType); 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(); object clone = FormatterServices.GetUninitializedObject(originalType); 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