transferring one object properties values to another one
Asked Answered
S

4

5

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() { } 
Shiller answered 18/3, 2012 at 20:51 Comment(1)
I don't think System.Reflection.Emit is going to help you here. In your case, both the source and target objects exist at compile time and you are just copying the values of corresponding properties from one to the other. Emit would help you if you wanted (for instance) to create a target type at runtime.Popp
E
6

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 .

Enochenol answered 18/3, 2012 at 21:40 Comment(2)
I almost understand your code; But because of I'm a newbie student in C#, somethings are in ambiguity for me yet. Can you explain how can I use this for open generics? Something like this: TTarget Transfer<TSource>(TTarget target) where TTarget : class, new() I put this at original question. Thanks a lotShiller
Thanks to edit and guidance. Yep, I understand what are you talking about, and I know about it. But my question is about getting types and properties for generic types and how can I create expression for them. For example it seems these lines: var sourceParameter = Expression.Parameter(typeof(object), "source"); var targetParameter = Expression.Parameter(typeof(object), "target"); are not required if the method be an open generic method. Is right?Shiller
B
4

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 );
     }
}
Buchner answered 18/3, 2012 at 21:1 Comment(6)
I think your code won't work if source has some write-only properties. They are very rare and are a bad practice, but certainly possible.Enochenol
@Enochenol in the context of model mapping (ref, the OP's introduction) I think that it would be acceptable to assume that neither source nor target has write only properties.Buchner
@tvanfosson, I think that would be an acceptable assumption in almost any context, but I would a check for that anyway, to be on the safe side.Enochenol
@Enochenol depends on whether I'm designing a library or writing a helper for my own code. In the latter case, my tests would make it clear that you shouldn't call the method with write only properties on the source, since it would expect an exception to be thrown.Buchner
I like this simple solution. Just a small correction: this line needs a minor correction as shown here: targetProperty.***PropertyType.***IsAssignableFrom(property.PropertyType))Pulsation
@NikolaSchou nice catchBuchner
R
0

C# Reflection IL - Understanding how values are copied

The question is about cloning so the type of the source object is the same as the type of the target object (while as I understand you can have different types in source and target) but still this could be worth of analyzing.

Resht answered 18/3, 2012 at 21:4 Comment(0)
B
0

I wrote a blog post about how to do it (portuguese only, but you could read the code)

http://elemarjr.net/2012/02/27/um-helper-para-shallow-cloning-emitting-em-c/

You can get the code from:

https://github.com/ElemarJR/FluentIL/blob/master/demos/Cloning/src/Cloning/Cloning/ILCloner.cs

I think that to use Reflection.Emit is harder than needed. So, I wrote a open-source library, called FluentIL (www.fluentil.org) to make it easier. I used it here.

[]s

Bybidder answered 27/3, 2012 at 13:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.