transferring one object properties values to another one

时光毁灭记忆、已成空白 提交于 2019-12-05 02:04:05

问题


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() { } 

回答1:


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 .




回答2:


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 );
     }
}



回答3:


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.




回答4:


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



来源:https://stackoverflow.com/questions/9762190/transferring-one-object-properties-values-to-another-one

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!