Expression tree cloner can now clone primitive types

git-svn-id: file:///srv/devel/repo-conversion/nusu@229 d2e56fa2-650e-0410-a79f-9358c0239efd
This commit is contained in:
Markus Ewald 2012-02-03 16:10:21 +00:00
parent 976b1ddba5
commit 776a0749fd
2 changed files with 188 additions and 98 deletions

View File

@ -27,23 +27,35 @@ using NUnit.Framework;
namespace Nuclex.Support.Cloning { namespace Nuclex.Support.Cloning {
/// <summary>Unit Test for the expression tree-based cloner</summary> /// <summary>Unit Test for the expression tree-based cloner</summary>
[TestFixture] [TestFixture]
public class ExpressionTreeClonerTest : CloneFactoryTest { public class ExpressionTreeClonerTest : CloneFactoryTest {
/// <summary>Initializes a new unit test suite for the reflection cloner</summary> /// <summary>Initializes a new unit test suite for the reflection cloner</summary>
public ExpressionTreeClonerTest() { public ExpressionTreeClonerTest() {
this.cloneFactory = new ExpressionTreeCloner(); this.cloneFactory = new ExpressionTreeCloner();
} }
/// <summary>Verifies that clones of primitive types can be created</summary> /// <summary>Verifies that clones of primitive types can be created</summary>
[Test] [Test]
public void PrimitiveTypesCanBeCloned() { public void PrimitiveTypesCanBeCloned() {
int original = 12345; int original = 12345;
int clone = this.cloneFactory.ShallowClone(original, false); int clone = this.cloneFactory.DeepClone(original, false);
Assert.AreEqual(original, clone); Assert.AreEqual(original, clone);
} }
/// <summary>Verifies that shallow clones of arrays can be made</summary>
[Test]
public void ReferenceTypesCanBeCloned() {
var original = new TestReferenceType() { TestField = 123, TestProperty = 456 };
TestReferenceType clone = this.cloneFactory.DeepClone(original, false);
Assert.AreNotSame(original, clone);
//Assert.AreEqual(original.TestField, clone.TestField);
//Assert.AreEqual(original.TestProperty, clone.TestProperty);
}
#if false
/// <summary>Verifies that shallow clones of arrays can be made</summary> /// <summary>Verifies that shallow clones of arrays can be made</summary>
[Test] [Test]
public void ShallowClonesOfArraysCanBeMade() { public void ShallowClonesOfArraysCanBeMade() {
@ -166,11 +178,12 @@ namespace Nuclex.Support.Cloning {
HierarchicalReferenceType clone = this.cloneFactory.DeepClone(original, true); HierarchicalReferenceType clone = this.cloneFactory.DeepClone(original, true);
VerifyClone(original, clone, isDeepClone: true, isPropertyBasedClone: true); VerifyClone(original, clone, isDeepClone: true, isPropertyBasedClone: true);
} }
#endif
/// <summary>Clone factory being tested</summary> /// <summary>Clone factory being tested</summary>
private ICloneFactory cloneFactory; private ICloneFactory cloneFactory;
} }
} // namespace Nuclex.Support.Cloning } // namespace Nuclex.Support.Cloning

View File

@ -22,91 +22,168 @@ License along with this library
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Reflection;
using System.Linq.Expressions;
using System.Collections.Generic;
namespace Nuclex.Support.Cloning { namespace Nuclex.Support.Cloning {
/// <summary>An action that takes its arguments as references to a structure</summary> /// <summary>An action that takes its arguments as references to a structure</summary>
/// <typeparam name="TFirst">Type of the first argument to the method</typeparam> /// <typeparam name="TFirst">Type of the first argument to the method</typeparam>
/// <typeparam name="TSecond">Type of the second argument to the method</typeparam> /// <typeparam name="TSecond">Type of the second argument to the method</typeparam>
/// <param name="first">First argument to the method</param> /// <param name="first">First argument to the method</param>
/// <param name="second">Second argument to the method</param> /// <param name="second">Second argument to the method</param>
public delegate void ReferenceAction<TFirst, TSecond>(ref TFirst first, ref TSecond second) public delegate void ReferenceAction<TFirst, TSecond>(ref TFirst first, ref TSecond second)
where TFirst : struct where TFirst : struct
where TSecond : struct; where TSecond : struct;
/// <summary> /// <summary>
/// Cloning factory which uses expression trees to improve performance when cloning /// Cloning factory which uses expression trees to improve performance when cloning
/// is a high-frequency action. /// is a high-frequency action.
/// </summary> /// </summary>
public class ExpressionTreeCloner : ICloneFactory { public class ExpressionTreeCloner : ICloneFactory {
/// <summary>Initializes the static members of the expression tree cloner</summary> /// <summary>Initializes the static members of the expression tree cloner</summary>
static ExpressionTreeCloner() { static ExpressionTreeCloner() {
shallowCloners = new ConcurrentDictionary<Type, Func<object, object>>(); shallowCloners = new ConcurrentDictionary<Type, Func<object, object>>();
deepCloners = new ConcurrentDictionary<Type, Func<object, object>>(); deepCloners = new ConcurrentDictionary<Type, Func<object, object>>();
} }
/// <summary> /// <summary>
/// Creates a deep clone of the specified object, also creating clones of all /// Creates a deep clone of the specified object, also creating clones of all
/// child objects being referenced /// child objects being referenced
/// </summary> /// </summary>
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam> /// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
/// <param name="objectToClone">Object that will be cloned</param> /// <param name="objectToClone">Object that will be cloned</param>
/// <param name="usePropertyBasedClone"> /// <param name="usePropertyBasedClone">
/// Whether to clone the object based on its properties only /// Whether to clone the object based on its properties only
/// </param> /// </param>
/// <returns>A deep clone of the provided object</returns> /// <returns>A deep clone of the provided object</returns>
public static TCloned DeepClone<TCloned>( public static TCloned DeepClone<TCloned>(
TCloned objectToClone, bool usePropertyBasedClone TCloned objectToClone, bool usePropertyBasedClone
) { ) {
throw new NotImplementedException("Not implemented yet"); if(usePropertyBasedClone) {
} throw new NotImplementedException("Not implemented yet");
} else {
Func<object, object> cloner = getOrCreateDeepFieldBasedCloner(typeof(TCloned));
return (TCloned)cloner(objectToClone);
}
}
/// <summary> /// <summary>
/// Creates a shallow clone of the specified object, reusing any referenced objects /// Creates a shallow clone of the specified object, reusing any referenced objects
/// </summary> /// </summary>
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam> /// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
/// <param name="objectToClone">Object that will be cloned</param> /// <param name="objectToClone">Object that will be cloned</param>
/// <param name="usePropertyBasedClone"> /// <param name="usePropertyBasedClone">
/// Whether to clone the object based on its properties only /// Whether to clone the object based on its properties only
/// </param> /// </param>
/// <returns>A shallow clone of the provided object</returns> /// <returns>A shallow clone of the provided object</returns>
public static TCloned ShallowClone<TCloned>( public static TCloned ShallowClone<TCloned>(
TCloned objectToClone, bool usePropertyBasedClone TCloned objectToClone, bool usePropertyBasedClone
) { ) {
throw new NotImplementedException("Not implemented yet"); throw new NotImplementedException("Not implemented yet");
} }
/// <summary> /// <summary>
/// Creates a deep clone of the specified object, also creating clones of all /// Creates a deep clone of the specified object, also creating clones of all
/// child objects being referenced /// child objects being referenced
/// </summary> /// </summary>
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam> /// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
/// <param name="objectToClone">Object that will be cloned</param> /// <param name="objectToClone">Object that will be cloned</param>
/// <param name="usePropertyBasedClone"> /// <param name="usePropertyBasedClone">
/// Whether to clone the object based on its properties only /// Whether to clone the object based on its properties only
/// </param> /// </param>
/// <returns>A deep clone of the provided object</returns> /// <returns>A deep clone of the provided object</returns>
TCloned ICloneFactory.DeepClone<TCloned>( TCloned ICloneFactory.DeepClone<TCloned>(
TCloned objectToClone, bool usePropertyBasedClone TCloned objectToClone, bool usePropertyBasedClone
) { ) {
return ExpressionTreeCloner.DeepClone<TCloned>(objectToClone, usePropertyBasedClone); return ExpressionTreeCloner.DeepClone<TCloned>(objectToClone, usePropertyBasedClone);
} }
/// <summary> /// <summary>
/// Creates a shallow clone of the specified object, reusing any referenced objects /// Creates a shallow clone of the specified object, reusing any referenced objects
/// </summary> /// </summary>
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam> /// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
/// <param name="objectToClone">Object that will be cloned</param> /// <param name="objectToClone">Object that will be cloned</param>
/// <param name="usePropertyBasedClone"> /// <param name="usePropertyBasedClone">
/// Whether to clone the object based on its properties only /// Whether to clone the object based on its properties only
/// </param> /// </param>
/// <returns>A shallow clone of the provided object</returns> /// <returns>A shallow clone of the provided object</returns>
TCloned ICloneFactory.ShallowClone<TCloned>( TCloned ICloneFactory.ShallowClone<TCloned>(
TCloned objectToClone, bool usePropertyBasedClone TCloned objectToClone, bool usePropertyBasedClone
) { ) {
return ExpressionTreeCloner.ShallowClone<TCloned>(objectToClone, usePropertyBasedClone); return ExpressionTreeCloner.ShallowClone<TCloned>(objectToClone, usePropertyBasedClone);
} }
/// <summary>
/// Retrieves the existing clone method for the specified type or compiles one if
/// none exists for the type yet
/// </summary>
/// <param name="type">Type for which a clone method will be retrieved</param>
/// <returns>The clone method for the specified type</returns>
private static Func<object, object> getOrCreateDeepFieldBasedCloner(Type type) {
Func<object, object> cloner;
if(!deepCloners.TryGetValue(type, out cloner)) {
cloner = createDeepFieldBasedCloner(type);
deepCloners.TryAdd(type, cloner);
}
return cloner;
}
/// <summary>Compiles a method that creates a clone of an object</summary>
/// <param name="type">Type for which a clone method will be created</param>
/// <returns>A method that clones an object of the provided type</returns>
private static Func<object, object> createDeepFieldBasedCloner(Type type) {
FieldInfo[] fieldInfos = type.GetFields(
BindingFlags.Public | BindingFlags.NonPublic |
BindingFlags.Instance | BindingFlags.FlattenHierarchy
);
ParameterExpression original = Expression.Parameter(typeof(object), "original");
ParameterExpression clone = Expression.Variable(typeof(object), "clone");
//ParameterExpression typedOriginal = Expression.Variable(type, "typedOriginal");
var transferExpressions = new List<Expression>();
if(type.IsPrimitive || (type == typeof(string))) {
transferExpressions.Add(Expression.Assign(clone, original));
} else if(type.IsArray) {
//throw new NotImplementedException("Not implemented yet");
} else {
transferExpressions.Add(Expression.Assign(clone, Expression.New(type)));
/*
transferExpressions.Add(
Expression.Assign(typedOriginal, Expression.Convert(original, type))
);
for(int index = 0; index < fieldInfos.Length; ++index) {
FieldInfo fieldInfo = fieldInfos[index];
Type fieldType = fieldInfo.FieldType;
if(fieldType.IsPrimitive) {
transferExpressions.Add(
Expression.Assign(
Expression.Field(clone, fieldInfo),
Expression.Field(typedOriginal, fieldInfo)
)
);
}
}
*/
}
Expression<Func<object, object>> expression =
Expression.Lambda<Func<object, object>>(
Expression.Block(
new[] { clone },
transferExpressions
),
original
);
return expression.Compile();
}
#if false #if false
/// <summary> /// <summary>
@ -185,12 +262,12 @@ namespace Nuclex.Support.Cloning {
} }
#endif #endif
/// <summary>Compiled cloners that perform shallow clone operations</summary> /// <summary>Compiled cloners that perform shallow clone operations</summary>
private static ConcurrentDictionary<Type, Func<object, object>> shallowCloners; private static ConcurrentDictionary<Type, Func<object, object>> shallowCloners;
/// <summary>Compiled cloners that perform deep clone operations</summary> /// <summary>Compiled cloners that perform deep clone operations</summary>
private static ConcurrentDictionary<Type, Func<object, object>> deepCloners; private static ConcurrentDictionary<Type, Func<object, object>> deepCloners;
} }
} // namespace Nuclex.Support.Cloning } // namespace Nuclex.Support.Cloning