Expression tree cloner can now clone multi-dimensional arrays
git-svn-id: file:///srv/devel/repo-conversion/nusu@232 d2e56fa2-650e-0410-a79f-9358c0239efd
This commit is contained in:
		
							parent
							
								
									1307976597
								
							
						
					
					
						commit
						79063f59c1
					
				
					 2 changed files with 394 additions and 193 deletions
				
			
		|  | @ -55,6 +55,16 @@ namespace Nuclex.Support.Cloning { | ||||||
| 			Assert.AreEqual(original.TestProperty, clone.TestProperty); | 			Assert.AreEqual(original.TestProperty, clone.TestProperty); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		/// <summary>Verifies that shallow clones of arrays can be made</summary> | ||||||
|  | 		[Test] | ||||||
|  | 		public void PrimitiveArraysCanBeCloned() { | ||||||
|  | 			var original = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; | ||||||
|  | 			int[] clone = this.cloneFactory.DeepClone(original, false); | ||||||
|  | 
 | ||||||
|  | 			Assert.AreNotSame(original, clone); | ||||||
|  | 			CollectionAssert.AreEqual(original, clone); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
| #if false | #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] | ||||||
|  | @ -66,20 +76,22 @@ namespace Nuclex.Support.Cloning { | ||||||
| 
 | 
 | ||||||
|       Assert.AreSame(original[0], clone[0]); |       Assert.AreSame(original[0], clone[0]); | ||||||
|     } |     } | ||||||
| //#endif | #endif | ||||||
| 		/// <summary>Verifies that deep clones of arrays can be made</summary> | 		/// <summary>Verifies that deep clones of arrays can be made</summary> | ||||||
| 		[Test] | 		[Test] | ||||||
| 		public void DeepClonesOfArraysCanBeMade() { | 		public void DeepClonesOfArraysCanBeMade() { | ||||||
|       var original = new TestReferenceType[] { | 			var original = new TestReferenceType[,] { | ||||||
|  | 				{ | ||||||
| 					new TestReferenceType() { TestField = 123, TestProperty = 456 } | 					new TestReferenceType() { TestField = 123, TestProperty = 456 } | ||||||
|       }; |  | ||||||
|       TestReferenceType[] clone = this.cloneFactory.DeepClone(original, false); |  | ||||||
| 
 |  | ||||||
|       Assert.AreNotSame(original[0], clone[0]); |  | ||||||
|       Assert.AreEqual(original[0].TestField, clone[0].TestField); |  | ||||||
|       Assert.AreEqual(original[0].TestProperty, clone[0].TestProperty); |  | ||||||
| 				} | 				} | ||||||
| //#if false |       }; | ||||||
|  | 			TestReferenceType[,] clone = this.cloneFactory.DeepClone(original, false); | ||||||
|  | 
 | ||||||
|  | 			Assert.AreNotSame(original[0, 0], clone[0, 0]); | ||||||
|  | 			//Assert.AreEqual(original[0,0].TestField, clone[0,0].TestField); | ||||||
|  | 			//Assert.AreEqual(original[0,0].TestProperty, clone[0,0].TestProperty); | ||||||
|  | 		} | ||||||
|  | #if false | ||||||
|     /// <summary>Verifies that deep clones of a generic list can be made</summary> |     /// <summary>Verifies that deep clones of a generic list can be made</summary> | ||||||
|     [Test] |     [Test] | ||||||
|     public void GenericListsCanBeCloned() { |     public void GenericListsCanBeCloned() { | ||||||
|  |  | ||||||
|  | @ -134,6 +134,209 @@ namespace Nuclex.Support.Cloning { | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		/// <summary> | ||||||
|  | 		///   Generates state transfer expressions to copy an array of primitive types | ||||||
|  | 		/// </summary> | ||||||
|  | 		/// <param name="clonedType">Type of array that will be cloned</param> | ||||||
|  | 		/// <param name="original">Variable expression for the original array</param> | ||||||
|  | 		/// <param name="variables">Receives variables used by the transfer expressions</param> | ||||||
|  | 		/// <param name="transferExpressions">Receives the generated transfer expressions</param> | ||||||
|  | 		private static void generatePrimitiveArrayTransferExpressions( | ||||||
|  | 			Type clonedType, | ||||||
|  | 			ParameterExpression original, | ||||||
|  | 			ICollection<ParameterExpression> variables, | ||||||
|  | 			ICollection<Expression> transferExpressions | ||||||
|  | 		) { | ||||||
|  | 			// We need a temporary variable because the IfThen expression is not suitable | ||||||
|  | 			// for returning values | ||||||
|  | 			ParameterExpression clone = Expression.Variable(clonedType, "clone"); | ||||||
|  | 			variables.Add(clone); | ||||||
|  | 
 | ||||||
|  | 			// If the array referenced by 'original' is not null, call Array.Clone() on it | ||||||
|  | 			// and assign the result to our temporary variable | ||||||
|  | 			MethodInfo arrayCloneMethodInfo = typeof(Array).GetMethod("Clone"); | ||||||
|  | 			transferExpressions.Add( | ||||||
|  | 				Expression.IfThen( | ||||||
|  | 					Expression.NotEqual(original, Expression.Constant(null)), | ||||||
|  | 					Expression.Assign( | ||||||
|  | 						clone, | ||||||
|  | 						Expression.Convert( | ||||||
|  | 							Expression.Call(original, arrayCloneMethodInfo), | ||||||
|  | 							clonedType | ||||||
|  | 						) | ||||||
|  | 					) | ||||||
|  | 				) | ||||||
|  | 			); | ||||||
|  | 
 | ||||||
|  | 			// Set the return value to the temporary variable | ||||||
|  | 			transferExpressions.Add(clone); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		/// <summary> | ||||||
|  | 		///   Generates state transfer expressions to copy an array of complex types | ||||||
|  | 		/// </summary> | ||||||
|  | 		/// <param name="clonedType">Type of array that will be cloned</param> | ||||||
|  | 		/// <param name="original">Variable expression for the original array</param> | ||||||
|  | 		/// <param name="variables">Receives variables used by the transfer expressions</param> | ||||||
|  | 		/// <param name="transferExpressions">Receives the generated transfer expressions</param> | ||||||
|  | 		private static void generateComplexArrayTransferExpressions( | ||||||
|  | 			Type clonedType, | ||||||
|  | 			ParameterExpression original, | ||||||
|  | 			IList<ParameterExpression> variables, | ||||||
|  | 			ICollection<Expression> transferExpressions | ||||||
|  | 		) { | ||||||
|  | 			// We need a temporary variable because the IfThen expression is not suitable | ||||||
|  | 			// for returning values | ||||||
|  | 			ParameterExpression clone = Expression.Variable(clonedType, "clone"); | ||||||
|  | 			variables.Add(clone); | ||||||
|  | 
 | ||||||
|  | 			int dimensionCount = clonedType.GetArrayRank(); | ||||||
|  | 			int baseVariableIndex = variables.Count; | ||||||
|  | 			var arrayTransferExpressions = new List<Expression>(); | ||||||
|  | 			Type elementType = clonedType.GetElementType(); | ||||||
|  | 
 | ||||||
|  | 			// Retrieve the length of each of the array's dimensions | ||||||
|  | 			MethodInfo arrayGetLengthMethodInfo = typeof(Array).GetMethod("GetLength"); | ||||||
|  | 			for(int index = 0; index < dimensionCount; ++index) { | ||||||
|  | 				ParameterExpression length = Expression.Variable(typeof(int)); | ||||||
|  | 				variables.Add(length); | ||||||
|  | 				arrayTransferExpressions.Add( | ||||||
|  | 					Expression.Assign( | ||||||
|  | 						length, | ||||||
|  | 						Expression.Call(original, arrayGetLengthMethodInfo, Expression.Constant(index)) | ||||||
|  | 					) | ||||||
|  | 				); | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			// Create a new array of identical size | ||||||
|  | 			switch(dimensionCount) { | ||||||
|  | 				case 1: { | ||||||
|  | 					MethodInfo arrayCreateInstanceMethodInfo = typeof(Array).GetMethod( | ||||||
|  | 						"CreateInstance", new Type[] { typeof(Type), typeof(int) } | ||||||
|  | 					); | ||||||
|  | 					arrayTransferExpressions.Add( | ||||||
|  | 						Expression.Assign( | ||||||
|  | 							clone, | ||||||
|  | 							Expression.Convert( | ||||||
|  | 								Expression.Call( | ||||||
|  | 									arrayCreateInstanceMethodInfo, | ||||||
|  | 									Expression.Constant(elementType), | ||||||
|  | 									variables[baseVariableIndex] | ||||||
|  | 								), | ||||||
|  | 								clonedType | ||||||
|  | 							) | ||||||
|  | 						) | ||||||
|  | 					); | ||||||
|  | 					break; | ||||||
|  | 				} | ||||||
|  | 				case 2: { | ||||||
|  | 					MethodInfo arrayCreateInstanceMethodInfo = typeof(Array).GetMethod( | ||||||
|  | 						"CreateInstance", new Type[] { typeof(Type), typeof(int), typeof(int) } | ||||||
|  | 					); | ||||||
|  | 					arrayTransferExpressions.Add( | ||||||
|  | 						Expression.Assign( | ||||||
|  | 							clone, | ||||||
|  | 							Expression.Convert( | ||||||
|  | 								Expression.Call( | ||||||
|  | 									arrayCreateInstanceMethodInfo, | ||||||
|  | 									Expression.Constant(elementType), | ||||||
|  | 									variables[baseVariableIndex], | ||||||
|  | 									variables[baseVariableIndex + 1] | ||||||
|  | 								), | ||||||
|  | 								clonedType | ||||||
|  | 							) | ||||||
|  | 						) | ||||||
|  | 					); | ||||||
|  | 					break; | ||||||
|  | 				} | ||||||
|  | 				case 3: { | ||||||
|  | 					MethodInfo arrayCreateInstanceMethodInfo = typeof(Array).GetMethod( | ||||||
|  | 						"CreateInstance", new Type[] { typeof(Type), typeof(int), typeof(int), typeof(int) } | ||||||
|  | 					); | ||||||
|  | 					arrayTransferExpressions.Add( | ||||||
|  | 						Expression.Assign( | ||||||
|  | 							clone, | ||||||
|  | 							Expression.Convert( | ||||||
|  | 								Expression.Call( | ||||||
|  | 									arrayCreateInstanceMethodInfo, | ||||||
|  | 									Expression.Constant(elementType), | ||||||
|  | 									variables[baseVariableIndex], | ||||||
|  | 									variables[baseVariableIndex + 1], | ||||||
|  | 									variables[baseVariableIndex + 2] | ||||||
|  | 								), | ||||||
|  | 								clonedType | ||||||
|  | 							) | ||||||
|  | 						) | ||||||
|  | 					); | ||||||
|  | 					break; | ||||||
|  | 				} | ||||||
|  | 				default: { | ||||||
|  | 					throw new InvalidOperationException("Unsupported array dimension count"); | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			// Only execute the array transfer expressions if the array is not null | ||||||
|  | 			transferExpressions.Add( | ||||||
|  | 				Expression.IfThen( | ||||||
|  | 					Expression.NotEqual(original, Expression.Constant(null)), | ||||||
|  | 					Expression.Block(arrayTransferExpressions) | ||||||
|  | 				) | ||||||
|  | 			); | ||||||
|  | 
 | ||||||
|  | 			// Set the return value to the temporary variable | ||||||
|  | 			transferExpressions.Add(clone); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		/// <summary>Generates state transfer expressions to copy a complex type</summary> | ||||||
|  | 		/// <param name="clonedType">Complex type that will be cloned</param> | ||||||
|  | 		/// <param name="original">Variable expression for the original instance</param> | ||||||
|  | 		/// <param name="variables">Receives variables used by the transfer expressions</param> | ||||||
|  | 		/// <param name="transferExpressions">Receives the generated transfer expressions</param> | ||||||
|  | 		private static void generateComplexTypeTransferExpressions( | ||||||
|  | 			Type clonedType, | ||||||
|  | 			ParameterExpression original, | ||||||
|  | 			ICollection<ParameterExpression> variables, | ||||||
|  | 			ICollection<Expression> transferExpressions | ||||||
|  | 		) { | ||||||
|  | 			// We need a temporary variable because the IfThen expression is not suitable | ||||||
|  | 			// for returning values | ||||||
|  | 			ParameterExpression clone = Expression.Variable(clonedType, "clone"); | ||||||
|  | 			variables.Add(clone); | ||||||
|  | 
 | ||||||
|  | 			var complexTransferExpressions = new List<Expression>(); | ||||||
|  | 
 | ||||||
|  | 			complexTransferExpressions.Add(Expression.Assign(clone, Expression.New(clonedType))); | ||||||
|  | 
 | ||||||
|  | 			FieldInfo[] fieldInfos = clonedType.GetFields( | ||||||
|  | 				BindingFlags.Public | BindingFlags.NonPublic | | ||||||
|  | 				BindingFlags.Instance | BindingFlags.FlattenHierarchy | ||||||
|  | 			); | ||||||
|  | 			for(int index = 0; index < fieldInfos.Length; ++index) { | ||||||
|  | 				FieldInfo fieldInfo = fieldInfos[index]; | ||||||
|  | 				Type fieldType = fieldInfo.FieldType; | ||||||
|  | 
 | ||||||
|  | 				if(fieldType.IsPrimitive) { | ||||||
|  | 					complexTransferExpressions.Add( | ||||||
|  | 						Expression.Assign( | ||||||
|  | 							Expression.Field(clone, fieldInfo), | ||||||
|  | 							Expression.Field(original, fieldInfo) | ||||||
|  | 						) | ||||||
|  | 					); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			transferExpressions.Add( | ||||||
|  | 				Expression.IfThen( | ||||||
|  | 					Expression.NotEqual(original, Expression.Constant(null)), | ||||||
|  | 					Expression.Block(complexTransferExpressions) | ||||||
|  | 				) | ||||||
|  | 			); | ||||||
|  | 
 | ||||||
|  | 			// Set the return value to the temporary variable | ||||||
|  | 			transferExpressions.Add(clone); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
| 		/// <summary>Compiles a method that creates a clone of an object</summary> | 		/// <summary>Compiles a method that creates a clone of an object</summary> | ||||||
| 		/// <typeparam name="TCloned">Type for which a clone method will be created</typeparam> | 		/// <typeparam name="TCloned">Type for which a clone method will be created</typeparam> | ||||||
| 		/// <returns>A method that clones an object of the provided type</returns> | 		/// <returns>A method that clones an object of the provided type</returns> | ||||||
|  | @ -144,55 +347,41 @@ namespace Nuclex.Support.Cloning { | ||||||
| 			ParameterExpression clone = Expression.Variable(typeof(TCloned), "clone"); | 			ParameterExpression clone = Expression.Variable(typeof(TCloned), "clone"); | ||||||
| 
 | 
 | ||||||
| 			var transferExpressions = new List<Expression>(); | 			var transferExpressions = new List<Expression>(); | ||||||
|  | 			var variables = new List<ParameterExpression>(); | ||||||
| 
 | 
 | ||||||
| 			if(clonedType.IsPrimitive || (clonedType == typeof(string))) { | 			if(clonedType.IsPrimitive || (clonedType == typeof(string))) { | ||||||
| 				transferExpressions.Add(original); // primitives are copied on assignment | 				transferExpressions.Add(original); // primitives are copied on assignment | ||||||
| 			} else if(clonedType.IsArray) { | 			} else if(clonedType.IsArray) { | ||||||
| 				Type elementType = clonedType.GetElementType(); | 				Type elementType = clonedType.GetElementType(); | ||||||
| 				if(elementType.IsPrimitive || (elementType == typeof(string))) { | 				if(elementType.IsPrimitive || (elementType == typeof(string))) { | ||||||
|           MethodInfo arrayCloneMethodInfo = typeof(Array).GetMethod("Clone"); | 					generatePrimitiveArrayTransferExpressions( | ||||||
|           transferExpressions.Add( | 						clonedType, original, variables, transferExpressions | ||||||
|             Expression.Convert( |  | ||||||
|               Expression.Call(original, arrayCloneMethodInfo), |  | ||||||
|               clonedType |  | ||||||
|             ) |  | ||||||
| 					); | 					); | ||||||
| 				} else { | 				} else { | ||||||
|  | 					generateComplexArrayTransferExpressions( | ||||||
|  | 						clonedType, original, variables, transferExpressions | ||||||
|  | 					); | ||||||
| 				} | 				} | ||||||
| 
 |  | ||||||
| 			} else { | 			} else { | ||||||
|         transferExpressions.Add(Expression.Assign(clone, Expression.New(clonedType))); | 				generateComplexTypeTransferExpressions( | ||||||
| 
 | 					clonedType, original, variables, transferExpressions | ||||||
|         FieldInfo[] fieldInfos = clonedType.GetFields( |  | ||||||
|           BindingFlags.Public | BindingFlags.NonPublic | |  | ||||||
|           BindingFlags.Instance | BindingFlags.FlattenHierarchy |  | ||||||
|         ); |  | ||||||
| 
 |  | ||||||
|         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(original, fieldInfo) |  | ||||||
|               ) |  | ||||||
| 				); | 				); | ||||||
| 			} | 			} | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         transferExpressions.Add(clone); | 			Expression<Func<TCloned, TCloned>> expression; | ||||||
|       } | 			if(variables.Count > 0) { | ||||||
| 
 | 				expression = Expression.Lambda<Func<TCloned, TCloned>>( | ||||||
|       Expression<Func<TCloned, TCloned>> expression = | 					Expression.Block(variables, transferExpressions), original | ||||||
|         Expression.Lambda<Func<TCloned, TCloned>>( |  | ||||||
|           Expression.Block( |  | ||||||
|             new[] { clone }, |  | ||||||
|             transferExpressions |  | ||||||
|           ), |  | ||||||
|           original |  | ||||||
| 				); | 				); | ||||||
|  | 			} else if(transferExpressions.Count == 1) { | ||||||
|  | 				expression = Expression.Lambda<Func<TCloned, TCloned>>( | ||||||
|  | 					transferExpressions[0], original | ||||||
|  | 				); | ||||||
|  | 			} else { | ||||||
|  | 				expression = Expression.Lambda<Func<TCloned, TCloned>>( | ||||||
|  | 					Expression.Block(transferExpressions), original | ||||||
|  | 				); | ||||||
|  | 			} | ||||||
| 
 | 
 | ||||||
| 			return expression.Compile(); | 			return expression.Compile(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue