#region CPL License /* Nuclex Framework Copyright (C) 2002-2012 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 #if !(XBOX360 || WINDOWS_PHONE) using System; using System.IO; using System.Reflection; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary; namespace Nuclex.Support.Cloning { /// Clones objects via serialization /// /// /// This type of cloning uses the binary formatter to persist the state of /// an object and then restores it into a clone. It has the advantage of even /// working with types that don't provide a default constructor, but is /// terribly slow. /// /// /// Inspired by the "A Generic Method for Deep Cloning in C# 3.0" article /// on CodeProject: http://www.codeproject.com/KB/cs/generic_deep_cloning.aspx /// /// public class SerializationCloner : ICloneFactory { #region class StaticSurrogateSelector /// Selects a static surrogate for any non-primitive types private class StaticSurrogateSelector : ISurrogateSelector { /// Initializes a new static surrogate selector /// Surrogate that will be selected public StaticSurrogateSelector(ISerializationSurrogate staticSurrogate) { this.staticSurrogate = staticSurrogate; } /// /// Sets the next selector to escalate to if this one can't provide a surrogate /// /// Selector to escalate to public void ChainSelector(ISurrogateSelector selector) { this.chainedSelector = selector; } /// /// Returns the selector this one will escalate to if it can't provide a surrogate /// /// The selector this one will escalate to public ISurrogateSelector GetNextSelector() { return this.chainedSelector; } /// Attempts to provides a surrogate for the specified type /// Type a surrogate will be provided for /// Context /// /// public ISerializationSurrogate GetSurrogate( Type type, StreamingContext context, out ISurrogateSelector selector ) { if(type.IsPrimitive || type.IsArray || (type == typeof(string))) { if(this.chainedSelector == null) { selector = null; return null; } else { return this.chainedSelector.GetSurrogate(type, context, out selector); } } else { selector = this; return this.staticSurrogate; } } /// Surrogate the that will be selected for any non-primitive types private readonly ISerializationSurrogate staticSurrogate; /// Surrogate selector to escalate to if no surrogate can be provided private ISurrogateSelector chainedSelector; } #endregion // class StaticSurrogateSelector #region class FieldSerializationSurrogate /// Serializes a type based on its fields private class FieldSerializationSurrogate : ISerializationSurrogate { /// Extracts the data to be serialized from an object /// Object that is being serialized /// Stores the serialized informations /// /// Provides additional informations about the serialization process /// public void GetObjectData( object objectToSerialize, SerializationInfo info, StreamingContext context ) { Type originalType = objectToSerialize.GetType(); FieldInfo[] fieldInfos = originalType.GetFieldInfosIncludingBaseClasses( BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ); for(int index = 0; index < fieldInfos.Length; ++index) { FieldInfo fieldInfo = fieldInfos[index]; info.AddValue(fieldInfo.Name, fieldInfo.GetValue(objectToSerialize)); } } /// Reinserts saved data into a deserializd object /// Object the saved data will be inserted into /// Contains the serialized informations /// /// Provides additional informations about the serialization process /// /// Surrogate selector that specified this surrogate /// The deserialized object public object SetObjectData( object deserializedObject, SerializationInfo info, StreamingContext context, ISurrogateSelector selector ) { Type originalType = deserializedObject.GetType(); FieldInfo[] fieldInfos = originalType.GetFieldInfosIncludingBaseClasses( BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ); for(int index = 0; index < fieldInfos.Length; ++index) { FieldInfo fieldInfo = fieldInfos[index]; fieldInfo.SetValue(deserializedObject, info.GetValue(fieldInfo.Name, fieldInfo.FieldType)); } return deserializedObject; } } #endregion // class FieldSerializationSurrogate #region class PropertySerializationSurrogate /// Serializes a type based on its properties private class PropertySerializationSurrogate : ISerializationSurrogate { /// Extracts the data to be serialized from an object /// Object that is being serialized /// Stores the serialized informations /// /// Provides additional informations about the serialization process /// public void GetObjectData( object objectToSerialize, SerializationInfo info, StreamingContext context ) { Type originalType = objectToSerialize.GetType(); 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) { info.AddValue(propertyInfo.Name, propertyInfo.GetValue(objectToSerialize, null)); } } } /// Reinserts saved data into a deserializd object /// Object the saved data will be inserted into /// Contains the serialized informations /// /// Provides additional informations about the serialization process /// /// Surrogate selector that specified this surrogate /// The deserialized object public object SetObjectData( object deserializedObject, SerializationInfo info, StreamingContext context, ISurrogateSelector selector ) { Type originalType = deserializedObject.GetType(); 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) { propertyInfo.SetValue( deserializedObject, info.GetValue(propertyInfo.Name, propertyInfo.PropertyType), null ); } } return deserializedObject; } } #endregion // class PropertySerializationSurrogate /// Initializes the static members of the serialization-based cloner static SerializationCloner() { fieldBasedFormatter = new BinaryFormatter( new StaticSurrogateSelector(new FieldSerializationSurrogate()), new StreamingContext(StreamingContextStates.All) ); propertyBasedFormatter = new BinaryFormatter( new StaticSurrogateSelector(new PropertySerializationSurrogate()), new StreamingContext(StreamingContextStates.All) ); } /// /// 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) { if(typeof(TCloned).IsClass || typeof(TCloned).IsArray) { if(ReferenceEquals(objectToClone, null)) { return default(TCloned); } } using(var memoryStream = new MemoryStream()) { fieldBasedFormatter.Serialize(memoryStream, objectToClone); memoryStream.Position = 0; return (TCloned)fieldBasedFormatter.Deserialize(memoryStream); } } /// /// 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) { if(typeof(TCloned).IsClass || typeof(TCloned).IsArray) { if(ReferenceEquals(objectToClone, null)) { return default(TCloned); } } using(var memoryStream = new MemoryStream()) { propertyBasedFormatter.Serialize(memoryStream, objectToClone); memoryStream.Position = 0; return (TCloned)propertyBasedFormatter.Deserialize(memoryStream); } } /// /// 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) { throw new NotSupportedException("The serialization cloner cannot create shallow clones"); } /// /// 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) { throw new NotSupportedException("The serialization cloner cannot create shallow clones"); } /// /// 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 SerializationCloner.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 SerializationCloner.DeepPropertyClone(objectToClone); } /// Serializes objects by storing their fields private static readonly BinaryFormatter fieldBasedFormatter; /// Serializes objects by storing their properties private static readonly BinaryFormatter propertyBasedFormatter; } } // namespace Nuclex.Support.Cloning #endif // !(XBOX360 || WINDOWS_PHONE)