#region CPL License
/*
Nuclex Framework
Copyright (C) 2002-2014 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.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