16

Anyone have a suggestion for a good utility class that maps values from one object to another? I want a utility class that uses reflection and takes two objects and copies values from the 1st object to the second if there is a public property with the same name.

I have two entities that are generated from a web service proxy, so I can't change the parent class or impliment an interface or anything like that. But I know that the two objects have the same public properties.

BrokeMyLegBiking
  • 5,898
  • 14
  • 51
  • 66
  • 5
    Just for the record - the accepted answer will have more overheads than the others, since it uses raw reflection. Approaches like `AutoMapper` and `Expression` pre-compile this into IL at runtime, which can yield *significant* performance advantages. So if you are doing lots of this, perhaps avoid the basic reflection approach. – Marc Gravell Apr 13 '10 at 05:18

6 Answers6

41

Should be pretty simple to throw together...

public static void CopyPropertyValues(object source, object destination)
{
    var destProperties = destination.GetType().GetProperties();

    foreach (var sourceProperty in source.GetType().GetProperties())
    {
        foreach (var destProperty in destProperties)
        {
            if (destProperty.Name == sourceProperty.Name && 
        destProperty.PropertyType.IsAssignableFrom(sourceProperty.PropertyType))
            {
                destProperty.SetValue(destination, sourceProperty.GetValue(
                    source, new object[] { }), new object[] { });

                break;
            }
        }
    }
}
Adam Robinson
  • 182,639
  • 35
  • 285
  • 343
20

Jon Skeet and Marc Gravell have a library called MiscUtil. Inside MiscUtil.Reflection there is a class called PropertyCopy that does exactly what you describe. It only works for .NET 3.5.

It works by running over the public properties of the SourceType, matches them up by name with the public properties of the TargetType, makes sure that each property can be assigned from the source to the target and then creates and caches a copier function for those two types (so you don't do all this reflection every time). I've used it in production code and can vouch for its goodness.

What the hey, I figured I'd just post their concise code (it's less than 100 lines w/comments). The license for this code can be found here:

using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;

namespace MiscUtil.Reflection
{
    /// <summary>
    /// Generic class which copies to its target type from a source
    /// type specified in the Copy method. The types are specified
    /// separately to take advantage of type inference on generic
    /// method arguments.
    /// </summary>
    public static class PropertyCopy<TTarget> where TTarget : class, new()
    {
        /// <summary>
        /// Copies all readable properties from the source to a new instance
        /// of TTarget.
        /// </summary>
        public static TTarget CopyFrom<TSource>(TSource source) where TSource : class
        {
            return PropertyCopier<TSource>.Copy(source);
        }

        /// <summary>
        /// Static class to efficiently store the compiled delegate which can
        /// do the copying. We need a bit of work to ensure that exceptions are
        /// appropriately propagated, as the exception is generated at type initialization
        /// time, but we wish it to be thrown as an ArgumentException.
        /// </summary>
        private static class PropertyCopier<TSource> where TSource : class
        {
            private static readonly Func<TSource, TTarget> copier;
            private static readonly Exception initializationException;

            internal static TTarget Copy(TSource source)
            {
                if (initializationException != null)
                {
                    throw initializationException;
                }
                if (source == null)
                {
                    throw new ArgumentNullException("source");
                }
                return copier(source);
            }

            static PropertyCopier()
            {
                try
                {
                    copier = BuildCopier();
                    initializationException = null;
                }
                catch (Exception e)
                {
                    copier = null;
                    initializationException = e;
                }
            }

            private static Func<TSource, TTarget> BuildCopier()
            {
                ParameterExpression sourceParameter = Expression.Parameter(typeof(TSource), "source");
                var bindings = new List<MemberBinding>();
                foreach (PropertyInfo sourceProperty in typeof(TSource).GetProperties())
                {
                    if (!sourceProperty.CanRead)
                    {
                        continue;
                    }
                    PropertyInfo targetProperty = typeof(TTarget).GetProperty(sourceProperty.Name);
                    if (targetProperty == null)
                    {
                        throw new ArgumentException("Property " + sourceProperty.Name + " is not present and accessible in " + typeof(TTarget).FullName);
                    }
                    if (!targetProperty.CanWrite)
                    {
                        throw new ArgumentException("Property " + sourceProperty.Name + " is not writable in " + typeof(TTarget).FullName);
                    }
                    if (!targetProperty.PropertyType.IsAssignableFrom(sourceProperty.PropertyType))
                    {
                        throw new ArgumentException("Property " + sourceProperty.Name + " has an incompatible type in " + typeof(TTarget).FullName);
                    }
                    bindings.Add(Expression.Bind(targetProperty, Expression.Property(sourceParameter, sourceProperty)));
                }
                Expression initializer = Expression.MemberInit(Expression.New(typeof(TTarget)), bindings);
                return Expression.Lambda<Func<TSource,TTarget>>(initializer, sourceParameter).Compile();
            }
        }
    }
}
Restore the Data Dumps
  • 38,967
  • 12
  • 96
  • 122
  • 1
    @Marc Gravell: Like the Illuminati? :) – Restore the Data Dumps Apr 12 '10 at 21:10
  • a bit like that, but they don't have the ♦ ;-p (unless Jeff *is* the Illuminati...) – Marc Gravell Apr 12 '10 at 21:27
  • @Marc Gravell: . .. ... And suddenly their entire plan has become clear. – Restore the Data Dumps Apr 12 '10 at 23:39
  • Any idea how to update this to be able to copy from sql server char(1) column to a char property on my POCO? Right now I'm getting the error "Property EmailType has an incompatible type..." Thanks. – geoff swartz Oct 10 '16 at 19:45
  • Something to be careful of, the function runs into issues if you are trying to mix nullable and non-nullable types with the same name. The error that is given is a generic Verification Exception generated by the runtime.. took me a while to work out exactly what the problem was – GreatSamps Nov 02 '16 at 07:41
11

We use Automapper for this. It works really well.

Mark
  • 9,966
  • 7
  • 37
  • 39
  • Bugger. Took me forever to remember it was "AutoMapper" and not "AutoMap". Btw, searching for "automatically map" on Google sucks. –  Apr 12 '10 at 19:44
5

I've improved Robinson's answer and refactored it into an extension method on the Object type, very convenient:

    public static void CopyPropertyValues( this object destination, object source )
    {
        if ( !( destination.GetType ().Equals ( source.GetType () ) ) )
            throw new ArgumentException ( "Type mismatch" );
        if ( destination is IEnumerable )
        {
            var dest_enumerator = (destination as IEnumerable).GetEnumerator();
            var src_enumerator = (source as IEnumerable).GetEnumerator();
            while ( dest_enumerator.MoveNext () && src_enumerator.MoveNext () )
                dest_enumerator.Current.CopyPropertyValues ( src_enumerator.Current );
        }
        else
        {
            var destProperties = destination.GetType ().GetRuntimeProperties ();
            foreach ( var sourceProperty in source.GetType ().GetRuntimeProperties () )
            {
                foreach ( var destProperty in destProperties )
                {
                    if ( destProperty.Name == sourceProperty.Name 
                        && destProperty.PropertyType.GetTypeInfo ()
                            .IsAssignableFrom ( sourceProperty.PropertyType.GetTypeInfo () ) )
                    {
                        destProperty.SetValue ( destination, sourceProperty.GetValue (
                            source, new object[] { } ), new object[] { } );
                        break;
                    }
                }
            }
        }
    }
prmph
  • 7,616
  • 11
  • 37
  • 46
  • Suppose we have a list of object. we get an exception after while, dest_enumerator.Current, .......... "Enumeration has either not started or has already finished." your code is not work. – x19 Oct 17 '14 at 19:00
4

What about doing it with Json.net?

static T CopyPropertiesJson<T>(object source)
{
    string jsonsource = JsonConvert.SerializeObject(source);
    return JsonConvert.DeserializeObject<T>(jsonsource);
}
Tunaki
  • 132,869
  • 46
  • 340
  • 423
Ken Dudley
  • 131
  • 1
  • 2
2

I blogged about using Expression<T> to do this shortly after reading this article by Marc Gravell.

It looks (based on another answer) like it may be similar to the one in Jon Skeet and Marc's MiscUtil.

Roger Lipscombe
  • 89,048
  • 55
  • 235
  • 380
  • Funnily enough, I was invite to write that article after somebody liked an `Expression<>` based bit of code that *compares* objects property-by-property. I might be wrong, but probably this one: http://stackoverflow.com/questions/986572/hows-to-quick-check-if-data-transfer-two-objects-have-equal-properties-in-c/986617#986617 . – Marc Gravell Apr 12 '10 at 20:51
  • @jitbit fixed URL – Roger Lipscombe Nov 21 '17 at 18:22