Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

transferring one object properties values to another one

Before all, I know about AutoMapper, and I don't want to use it. Because I'm learning C# and I want to receive a deep view of it. So I'm trying to do this issue (explained below) myself.

However, I'm trying to create a property copier to cope values of one type's properties to another one, if the property has the same name and type and is readable from source and writable in target. I'm using type.GetProperties() method. Sampled method is here:

    static void Transfer(object source, object target) {

        var sourceType = source.GetType();
        var targetType = target.GetType();

        var sourceProps = sourceType.GetProperties(BindingFlags.Public | BindingFlags.Instance);

        var targetProps = (from t in targetType.GetProperties()
                           where t.CanWrite
                                 && (t.GetSetMethod().Attributes & MethodAttributes.Static) == 0
                           select t).ToList();

        foreach(var prop in sourceProps) {
            var value = prop.GetValue(source, null);
            var tProp = targetProps
                .FirstOrDefault(p => p.Name == prop.Name &&
                    p.PropertyType.IsAssignableFrom(prop.PropertyType));
            if(tProp != null)
                tProp.SetValue(target, value, null);
        }
    }

It works, but I read an answer at SO, that using System.Reflection.Emit and ILGenerator and late-bound delegates are more quickly and have a higher performance. But there was not more explanation or any link. Can you help me to understanding ways to speed up this code? or can you suggest me some links about Emit, ILGenerator, and late-bound delegates please? Or anything you think will help me to subject?

COMPELETE Q:

I understand and learn many things from @svick's answer. But now, if I want to use it as an open generic method, how can I do it? something like this:

public TTarget Transfer<TSource, TTarget>(TSource source) where TTarget : class, new() { } 

or an extension:

public static TTarget Transfer<TSource, TTarget>(this TSource source) where TTarget : class, new() { } 
like image 889
amiry jd Avatar asked Mar 18 '12 20:03

amiry jd


2 Answers

You could use Reflection.Emit to do this, but it's usually much easier to use Expressions and it gives you basically the same performance. Keep in mind that the performance benefit is there only if you cache the compiled code, for example in Dictionary<Tuple<Type, Type>, Action<object, object>>, which I'm not doing here.

static void Transfer(object source, object target)
{
    var sourceType = source.GetType();
    var targetType = target.GetType();

    var sourceParameter = Expression.Parameter(typeof(object), "source");
    var targetParameter = Expression.Parameter(typeof(object), "target");

    var sourceVariable = Expression.Variable(sourceType, "castedSource");
    var targetVariable = Expression.Variable(targetType, "castedTarget");

    var expressions = new List<Expression>();

    expressions.Add(Expression.Assign(sourceVariable, Expression.Convert(sourceParameter, sourceType)));
    expressions.Add(Expression.Assign(targetVariable, Expression.Convert(targetParameter, targetType)));

    foreach (var property in sourceType.GetProperties(BindingFlags.Public | BindingFlags.Instance))
    {
        if (!property.CanRead)
            continue;

        var targetProperty = targetType.GetProperty(property.Name, BindingFlags.Public | BindingFlags.Instance);
        if (targetProperty != null
                && targetProperty.CanWrite
                && targetProperty.PropertyType.IsAssignableFrom(property.PropertyType))
        {
            expressions.Add(
                Expression.Assign(
                    Expression.Property(targetVariable, targetProperty),
                    Expression.Convert(
                        Expression.Property(sourceVariable, property), targetProperty.PropertyType)));
        }
    }

    var lambda =
        Expression.Lambda<Action<object, object>>(
            Expression.Block(new[] { sourceVariable, targetVariable }, expressions),
            new[] { sourceParameter, targetParameter });

    var del = lambda.Compile();

    del(source, target);
}

If you have this, writing your generic method is simpple:

public TTarget Transfer<TSource, TTarget>(TSource source)
    where TTarget : class, new()
{
    var target = new TTarget();
    Transfer(source, target);
    return target;
} 

It could make sense to make the main worker method generic too and create Action<TSource, TTarget>, or even let it directly create the object and use Func<TSource, TTarget>. But if added caching as I suggested, it would mean you would have to use something like Dictionary<Tuple<Type, Type>, Delegate> and cast the delegate to the right type after retrieving it from the cache .

like image 87
svick Avatar answered Nov 15 '22 06:11

svick


You might consider only getting the properties (by name) that match on the target. That will significantly simplify your code.

foreach (var property in sourceType.GetProperties( BindingFlags.Public | BindingFlags.Instance))
{
     var targetProperty = targetType.GetProperty( property.Name, BindingFlags.Public | BindingFlags.Instance );
     if (targetProperty != null
          && targetProperty.CanWrite
          && targetProperty.PropertyType.IsAssignableFrom(property.PropertyType))
     {
         targetProperty.SetValue( target, property.GetValue(source, null), null );
     }
}
like image 28
tvanfosson Avatar answered Nov 15 '22 07:11

tvanfosson