Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 941
Hentov Avatar asked Dec 13 '25 14:12

Hentov


1 Answers

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

like image 110
Aleks Andreev Avatar answered Dec 15 '25 06:12

Aleks Andreev



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!