#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.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