June 21, 2011

Using .NET Reflection to copy the Properties of one class instance to another

I just spend the last hour trying to recall how I’d done this in the past and was disappointed to discover I hadn’t written a blog entry on it or sent anyone an email about it.  So here is a blog entry.

I’ve often found myself wanting to update an instance of a class based on another instance.  Writing something to copy each property one at a time is one option but it can bloat the code and you risk forgetting to update the code if you add a new property to the class.  Renaming or Deleting a property isn’t quite as bad as you’ll at least get an error at compile time.

So some example code:

var matchedInput = _router.Inputs.Where( i => i.Id == input.Id ).First();
matchedInput.Label = input.Label;
matchedInput.Color = input.Color;

As you can see if I decide to add a property (such as Image) I have to make sure I add it to this code or the method this snippet is taken from will fail to copy the Image property.

 

The better way, in my opinion, is to use reflection to copy the properties.  When I first needed to do this I came across a tutorial by Robbe Morris entitled .NET Reflection - Copy Class Properties.  This gave me the framework for what I needed to do and I even posted a comment on his blog with some of the code I used.

An updated version of his PropertyHandler class is pasted below:

/// <summary>
///
Used to copy properties from one instance of a class to another using System.Reflection
/// </summary>
public class PropertyHandler
{
   
/// <summary>
    /// Sets the properties.
    /// </summary>
    /// <param name="fromFields">From fields.</param>
    /// <param name="toFields">To fields.</param>
    /// <param name="fromRecord">From record.</param>
    /// <param name="toRecord">To record.</param>
    public static void SetProperties(PropertyInfo[] fromFields, PropertyInfo[] toFields, object fromRecord, object toRecord
)
    {
       
if (fromFields == null
)
        {
           
return
;
        }
       
if (toFields == null
)
        {
           
return
;
        }

       
foreach ( var fromField in fromFields
)
        {
           
var fromField1 = fromField
;
           
var matchedToFields = toFields.Where( toField => fromField1.Name == toField.Name
);
           
if ( !matchedToFields.Any
() )
            {
               
continue
;
            }
           
matchedToFields.First().SetValue( toRecord, fromField.GetValue( fromRecord, null ), null
);
        }
    }

   
/// <summary>
    /// Sets the properties.
    /// </summary>
    /// <param name="fromFields">From fields.</param>
    /// <param name="fromRecord">From record.</param>
    /// <param name="toRecord">To record.</param>
    public static void SetProperties(PropertyInfo[] fromFields, object fromRecord, object toRecord
)
    {
       
if (fromFields == null
)
        {
           
return
;
        }

       
foreach ( var fromField in fromFields
)
        {
           
fromField.SetValue( toRecord, fromField.GetValue( fromRecord, null ), null
);
        }
    }

   
/// <summary>
    /// Gets the settable properties of the given class
    /// </summary>
    /// <param name="type">The type.</param>
    /// <returns></returns>
    public static PropertyInfo[] GetSettableProperties( Type type
)
    {
       
var fromFields = type.GetProperties( BindingFlags.Public | BindingFlags.Instance
);
       
var list = new List<PropertyInfo>( fromFields.Length
);
       
list.AddRange( fromFields.Where( info => info.CanWrite
) );
       
return list.ToArray
();
    }
}

I simply removed the redundant try/catch blocks and simplified the code a bit from his original.  I also added a helper method that returns an array of PropertyInfo (needed for the fromFields) based on a given type.

The following is a sample class that is then using this to do it’s magic:

/// <summary>
///
   Class to represent an Output of the Router
/// </summary>
[DataContract( Namespace = "http://realitychecksystems.com/2011/05/schemas" )]
public class Output : Input
{
   
private static readonly PropertyInfo[] OutputSettableProperties = PropertyHandler.GetSettableProperties( typeof(Output
) );

   
private int? _routedInputId
;

   
/// <summary>
    ///   Gets or sets the routed input id.
    /// </summary>
    /// <value>The routed input id.</value>
    [DataMember( Order = 5, IsRequired = true
)]
   
public int? RoutedInputId
    {
       
get { return _routedInputId
; }
       
set
        {
           
if ( _routedInputId == value
)
            {
               
return
;
            }
           
_routedInputId = value
;
           
InvokePropertyChanged( "RoutedInputId"
);
        }
    }

   
/// <summary>
    /// Gets the settable properties for this class.
    /// </summary>
    /// <returns></returns>
    protected override PropertyInfo[] GetSettableProperties
()
    {
       
return OutputSettableProperties
;
    }
}

THE IMPORTANT BIT

The key thing to note here is the PropertyHandler.GetSettableProperties is a static method which will only execute once, via the field initializer of OutputSettableProperties, when the first instance of Output is created.  This ensures the “heavy lifting” of using reflection to get the properties is only done once.

The local GetSettableProperties method is used by the UpdateValues method of the parent class as follows:

/// <summary>
///
Updates the values of this instance based on another instance.
/// 
</summary>
///
 <param name="input">The input.</param>
public void UpdateValues( object input
)
{
   
PropertyHandler.SetProperties( GetSettableProperties(), input, this
);
}

This means both my parent class (Input) and the child (Output) can take advantage of the magic.

So the code example given earlier is now as follows:

var matchedInput = _router.Inputs.Where( i => i.Id == input.Id ).First();
matchedInput.UpdateValues( input );

Not only is the resulting code simpler to read it’s going to work even if I add, remove, or rename properties. 

As usual, there may be better approaches out there, but this is working for me.  If you have any suggested improvements feel free to pass them along in the comments below.

2 comments:

Anonymous said...

Thank you, very useful. Used your code within a FakeRepository class where I had a need to clone a generic POCO class to support a FindOriginalValue type method.

Jon said...

You're welcome. And thanks for letting me know someone out there found it useful. :)