Search code examples
c#copyexpression-treesgetter-setter

Dynamically copy certain properties between two class instances


I am attempting to write a piece of code that can take two instances of the same object, and copy some properties from the first one to the second one, dynamically. A little twist is that I only have access to the objects, through an interface they both inherit.

I have created a Copyable attribute that will be used to mark what properties can be copied.

I then managed to successfully do this using the PropertyInfo.GetMethod and PropertyInfo.SetMethod, however the resulting code is too slow. When comparing to statically assigning properties at compile time - this approach is ~20 times slower.

Here is my initial implementation using pure reflection.

using System;
using System.Linq;

namespace ConsoleApp58
{
    interface IInterface
    {
        int Id { get; set; }
    }

    [AttributeUsage(AttributeTargets.Property)]
    class CopyableAttribute : Attribute { }

    class Child : IInterface
    {
        public int Id { get; set; }

        [Copyable]
        public int CopyableProp { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var source = new Child() {Id = 1, CopyableProp = 42};
            var target = new Child() {Id = 2, CopyableProp = 0};

            CopyProps(source, target);
        }

        static void CopyProps(IInterface source, IInterface target)
        {
            var props = target.GetType()
                .GetProperties()
                .Where(p => p.IsDefined(typeof(CopyableAttribute), false))
                .ToArray();

            foreach (var prop in props)
            {
                var value = prop.GetMethod.Invoke(source, new object[] { });
                prop.SetMethod.Invoke(target, new [] {value});
            }
        }
    }
}

This works, but its slow, so I decided to attempt and create an expression tree that will build a lambda that can call the getters and setters, however I can't seem to make it work.

I tried following this SO question, however, that implementation relies on the fact that I know what's the type of my object that I'm taking the properties from.

However, in my case the properties are defined as part of child classes, and I have no access to them in my IInterface.

Hence, I'm asking here. Is there a (fast) way for me to copy the value of specific properties, between instances of two objects, by referring to them only through their common interface.


Solution

  • You can generate Action<IInterface, IInterface> by Expression API. Try this code:

    private static Expression<Action<IInterface, IInterface>> CreateCopyMethod(Type type)
    {
        var props = type
            .GetProperties()
            .Where(p => p.IsDefined(typeof(CopyableAttribute), false))
            .ToArray();
    
    
        var s = Expression.Parameter(typeof(IInterface), "s");
        var t = Expression.Parameter(typeof(IInterface), "t");
    
        var source = Expression.Variable(type, "source");
        var castToSource = Expression.Assign(source, Expression.Convert(s, type));
    
        var target = Expression.Variable(type, "target");
        var castToTarget = Expression.Assign(target, Expression.Convert(t, type));
    
        var instructions = new List<Expression>
        {
            castToSource, castToTarget
        };
        foreach (var property in props)
        {
            var left = Expression.Property(target, property);
            var right = Expression.Property(source, property);
            var assign = Expression.Assign(left, right);
    
            instructions.Add(assign);
        }
    
        var lambda = Expression.Lambda<Action<IInterface, IInterface>>(
            Expression.Block(
                new[] {source, target}, instructions),
            s, t);
        return lambda;
    }
    

    Usage

    IInterface src = new Child
    {
        CopyableProp = 42
    };
    IInterface dst = new Child();
    
    var copy = CreateCopyMethod(src.GetType()).Compile();
    copy(src, dst);
    
    Console.WriteLine(((Child)dst).CopyableProp); // 42
    

    To improve performance consider usage Dictionary<Type, Action<IInterface, IInterface>> to cache implementation of already generated methods