tags:

views:

875

answers:

4

What I want to do is

Given 2 objects A and B of type T, apply the properties values in A to the same propertie in B without doing an explicit assignment for each property.

I want to save code like this:

b.Nombre = a.Nombre;
b.Descripcion = a.Descripcion;
b.Imagen = a.Imagen;
b.Activo = a.Activo;

doing something like

a.ApplyProperties(b);

Is it possible?

A: 

There's ICloneable and object.MemberwiseClone (shallow copy) (these create a whole new object, so might not meet your requirements).

You could use reflection to do it yourself (inherit from a base class so you don't have to re-implement).

Or you could code generate it.

Cade Roux
A: 

If you want something like ApplyProperties, you could write an extension method on Object which would do what you need. Just realize that such an extension method wouldn't be "pure", or side-effect free. But if you need the capability, its a way to accomplish it.

jrista
A: 

you can use serialization to deep clone the object:

        public static T DeepClone<T>(this T objectToClone)
  where T: BaseClass
 {
  BinaryFormatter bFormatter = new BinaryFormatter();
  MemoryStream stream = new MemoryStream();
  bFormatter.Serialize(stream, objectToClone);
  stream.Seek(0, SeekOrigin.Begin);
  T clonedObject = (T)bFormatter.Deserialize(stream);
  return clonedObject;
 }

Classes would just have to be marked Serializable of course.

Andrija
+8  A: 

I have a type in MiscUtil called PropertyCopy which does something similar - although it creates a new instance of the target type and copies the properties into that. It would be easy to extend it to do the same thing with an existing instance though.

It doesn't require the types to be the same - it just copies all the readable properties from the "source" type to the "target" type. Of course if the types are the same, that's more likely to work :) It's a shallow copy, btw.

Let me know if you'd be interested in me extending the class for you. The syntax would be something like:

MyType instance1 = new MyType();
// Do stuff
MyType instance2 = new MyType();
// Do stuff

PropertyCopy.Copy(instance1, instance2);

(where Copy is a generic method called using type inference).

EDIT: Okay, I've extended the capabilities of the class. To copy from one instance to another, it uses simple PropertyInfo values at execution time - this is slower than using an expression tree, but the alternative would be to write a dynamic method, which I'm not too hot on. If performance is absolutely critical for you, let me know and I'll see what I can do. I'm not really ready to do a full MiscUtil release, but here's the updated code, including comments. I'm not going to rewrap them for the SO editor - just copy the whole chunk.

(I'd also probably redesign the API a bit in terms of naming if I were starting from scratch, but I don't want to break existing users...)

#if DOTNET35
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;

namespace MiscUtil.Reflection
{
    /// <summary>
    /// Non-generic class allowing properties to be copied from one instance
    /// to another existing instance of a potentially different type.
    /// </summary>
    public static class PropertyCopy
    {
        /// <summary>
        /// Copies all public, readable properties from the source object to the
        /// target. The target type does not have to have a parameterless constructor,
        /// as no new instance needs to be created.
        /// </summary>
        /// <remarks>Only the properties of the source and target types themselves
        /// are taken into account, regardless of the actual types of the arguments.</remarks>
        /// <typeparam name="TSource">Type of the source</typeparam>
        /// <typeparam name="TTarget">Type of the target</typeparam>
        /// <param name="source">Source to copy properties from</param>
        /// <param name="target">Target to copy properties to</param>
        public static void Copy<TSource, TTarget>(TSource source, TTarget target)
            where TSource : class
            where TTarget : class
        {
            PropertyCopier<TSource, TTarget>.Copy(source, target);
        }
    }

    /// <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, TTarget>.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.
    /// Note that this type we do not have a constructor constraint on TTarget, because
    /// we only use the constructor when we use the form which creates a new instance.
    /// </summary>
    internal static class PropertyCopier<TSource, TTarget>
    {
        /// <summary>
        /// Delegate to create a new instance of the target type given an instance of the
        /// source type. This is a single delegate from an expression tree.
        /// </summary>
        private static readonly Func<TSource, TTarget> creator;

        /// <summary>
        /// List of properties to grab values from. The corresponding targetProperties 
        /// list contains the same properties in the target type. Unfortunately we can't
        /// use expression trees to do this, because we basically need a sequence of statements.
        /// We could build a DynamicMethod, but that's significantly more work :) Please mail
        /// me if you really need this...
        /// </summary>
        private static readonly List<PropertyInfo> sourceProperties = new List<PropertyInfo>();
        private static readonly List<PropertyInfo> targetProperties = new List<PropertyInfo>();
        private static readonly Exception initializationException;

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

        internal static void Copy(TSource source, TTarget target)
        {
            if (initializationException != null)
            {
                throw initializationException;
            }
            if (source == null)
            {
                throw new ArgumentNullException("source");
            }
            for (int i = 0; i < sourceProperties.Count; i++)
            {
                targetProperties[i].SetValue(target, sourceProperties[i].GetValue(source, null), null);
            }

        }

        static PropertyCopier()
        {
            try
            {
                creator = BuildCreator();
                initializationException = null;
            }
            catch (Exception e)
            {
                creator = null;
                initializationException = e;
            }
        }

        private static Func<TSource, TTarget> BuildCreator()
        {
            ParameterExpression sourceParameter = Expression.Parameter(typeof(TSource), "source");
            var bindings = new List<MemberBinding>();
            foreach (PropertyInfo sourceProperty in typeof(TSource).GetProperties(BindingFlags.Public | BindingFlags.Instance))
            {
                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.GetSetMethod().Attributes & MethodAttributes.Static) != 0)
                {
                    throw new ArgumentException("Property " + sourceProperty.Name + " is static 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)));
                sourceProperties.Add(sourceProperty);
                targetProperties.Add(targetProperty);
            }
            Expression initializer = Expression.MemberInit(Expression.New(typeof(TTarget)), bindings);
            return Expression.Lambda<Func<TSource, TTarget>>(initializer, sourceParameter).Compile();
        }
    }
}
#endif
Jon Skeet
I want to use it with Linq to sql, for example when updating a record. I'm quite sure it has to work with an existing object, right? (that wich I get from database to update, I say)
eKek0
Jon, this looks great. Please extend class for that method :)
Andrija
Okay, I'll try to do it tonight - I'll edit my answer when I've done it.
Jon Skeet
Oh good, another anonymous downvote. That's useful... not.
Jon Skeet
Are downvotes supposed to be useful?! It wasn't me, but you're whining too much lately. Don't tempt me... :)
TheSoftwareJedi
Lately? I've been adding a comment requesting that downvotes be elaborated on (whenever I'm downvoted without a comment) for *ages*. And yes, they should be useful. Why take a course of action which is negative if you *don't* it's useful. See my blog post on voting for more about how I believe the voting system is used most effectively.
Jon Skeet
This fails when copying to an existing target if the target doesn't have it's own constructor, e.g. IMyType instance2 = new MyType(); Is this unavoidable?
@stovroz: Well, you'd have to supply the type that you actually wanted to instantiate.
Jon Skeet
@Jon: But the target is already instantiated. I thought your above enhancements were precisely to allow copying to an existing target, but it seems to want to create a new one anyway at: Expression.MemberInit(Expression.New(typeof(TTarget)), bindings).