One thing that I find tiresome when using the various Model/View patterns is the constant copying of data between the model and the view. Too often, I find myself writing code like this to copy data between an ICustomer and an ICustomerView;
// Copy the data from the customer to the view view.Address = customer.Address; view.Country = customer.Country; view.FirstName = customer.FirstName; view.LastName = customer.LastName; view.PostalCode = customer.PostalCode; view.Province = customer.Province;
I would much rather write something like one of the following lines;
// Copy the data from the customer to the view (using reflection in .NET 1.x) CopyHelper.Copy( typeof(ICustomer), customer, typeof(ICustomerView), view ); // Copy the data from the customer to the view (using reflection and generics in .NET 2.0) Copier<ICustomer>.Copy( customer ).To<ICustomerView>( view ); // Copy the data from the customer to the view (using extension methods in C# 3.0) customer.CopyTo<ICustomerView>( view );
It got me to thinking that there must be a better way, so I began writing code that would do the grunt work for me. Too often,
Over the next few days, I will blog about my thought process in developing this method and take it through the various iterations that can be seen in lines 2, 5 & 8 above.
Today, I will start with the .NET 1.x version. I will start with some design decisions;
- I want to be able to specify the types that I am copying between, not infer them using reflection. This way, I can use the interfaces, not the concrete classes when I am copying between the objects.
- For now, I am going to assume that if both interfaces have a non-static get/set property with the same name and type I will copy between them.
- I need to check that neither object is null and that I am not trying to copy an object over to itself.
This was simple enough. I created a static helper class called CopyHelper with one static Copy method. I use Type.GetProperties to get the non-static, public properties with getters and setters. If the name and type match, I use the GetValue and SetValue methods on the PropertyInfo class to copy the value across from one object to the next. This is the result;
#region Copyright © Alteridem Consulting 2008
//
// All rights are reserved. Reproduction or transmission in whole or in part, in
// any form or by any means, electronic, mechanical or otherwise, is prohibited
// without the prior written consent of the copyright owner.
//
// Filename: CopyHelper.cs
// Date: 06/06/2008 11:18 AM
// Author: Rob Prouse
//
#endregion
#region Using Directives
using System;
using System.Collections.Generic;
using System.Reflection;
#endregion
namespace Alteridem.ModelViewHelpers
{
public static class CopyHelper
{
#region Private Members
// We are interested in non-static, public properties with getters and setters
private const BindingFlags flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.GetProperty | BindingFlags.SetProperty;
#endregion
/// <summary>
/// Copies all public properties from one object to another.
/// </summary>
/// <param name="fromType">The type of the from object, preferably an interface. We could infer this using reflection, but this allows us to contrain the copy to an interface.</param>
/// <param name="from">The object to copy from</param>
/// <param name="toType">The type of the to object, preferably an interface. We could infer this using reflection, but this allows us to contrain the copy to an interface.</param>
/// <param name="to">The object to copy to</param>
public static void Copy( Type fromType, object from, Type toType, object to )
{
if ( fromType == null )
throw new ArgumentNullException( "fromType", "The type that you are copying from cannot be null" );
if ( from == null )
throw new ArgumentNullException( "from", "The object you are copying from cannot be null" );
if ( toType == null )
throw new ArgumentNullException( "toType", "The type that you are copying to cannot be null" );
if ( to == null )
throw new ArgumentNullException( "to", "The object you are copying to cannot be null" );
// Don't copy if they are the same object
if ( !ReferenceEquals( from, to ) )
{
// Get all of the public properties in the toType with getters and setters
Dictionary<string, PropertyInfo> toProperties = new Dictionary<string, PropertyInfo>();
PropertyInfo[] properties = toType.GetProperties( flags );
foreach ( PropertyInfo property in properties )
{
toProperties.Add( property.Name, property );
}
// Now get all of the public properties in the fromType with getters and setters
properties = fromType.GetProperties( flags );
foreach ( PropertyInfo fromProperty in properties )
{
// If a property matches in name and type, copy across
if ( toProperties.ContainsKey( fromProperty.Name ) )
{
PropertyInfo toProperty = toProperties[fromProperty.Name];
if ( toProperty.PropertyType == fromProperty.PropertyType )
{
object value = fromProperty.GetValue( from, null );
toProperty.SetValue( to, value, null );
}
}
}
}
}
}
}
Using this class, you can now write code like in line 2 of the second listing above. In my next post, I am going to extend this code using generics and give it a fluent interface for better readability.
I am very interested in hearing your feedback on this, so be sure to post to the comments. Do you think this is a good idea? Do you have suggestions for improvements? Let me know.