5

毕竟,我知道AutoMapper,我不想使用它。因为我正在学习C#,我想深入了解它。所以我正在尝试自己解决这个问题(如下所述)。

但是,我正在尝试创建一个属性复制器,以将一种类型的属性的值对应到另一种类型的属性,如果该属性具有相同的名称和类型并且可以从源读取并且在目标中可写。我正在使用type.GetProperties()方法。采样方法在这里:

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

它有效,但我在 SO 上阅读了一个答案,即使用System.Reflection.Emit后期绑定的代表更快并且性能更高ILGenerator。但是没有更多的解释或任何链接。你能帮我理解加速这段代码的方法吗?或者你能给我一些关于、、和迟到的代表的链接吗?或者任何你认为能帮助我服从的东西?EmitILGenerator

完成问:

我从@svick 的回答中了解并学到了很多东西。但是现在,如果我想将它用作一个开放的泛型方法,我该怎么做呢?像这样的东西:

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

或扩展名:

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

4 回答 4

6

可以使用 Reflection.Emit 来执行此操作,但使用Expressions 通常更容易,并且它为您提供基本相同的性能。请记住,仅当您缓存已编译的代码时,性能优势才会出现,例如在 中Dictionary<Tuple<Type, Type>, Action<object, object>>,我在这里没有这样做。

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

如果你有这个,编写你的泛型方法很简单:

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

Action<TSource, TTarget>将主要的工作方法也设为通用并创建,甚至让它直接创建对象并使用是有意义的Func<TSource, TTarget>。但是,如果按照我的建议添加缓存,则意味着您必须在从缓存中检索委托后使用类似的东西Dictionary<Tuple<Type, Type>, Delegate>并将委托转换为正确的类型。

于 2012-03-18T21:40:02.687 回答
4

您可能会考虑仅获取与目标匹配的属性(按名称)。这将大大简化您的代码。

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 );
     }
}
于 2012-03-18T21:01:39.263 回答
0

我写了一篇关于如何做的博客文章(仅限葡萄牙语,但您可以阅读代码)

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

您可以从以下位置获取代码:

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

我认为使用 Reflection.Emit 比需要的更难。因此,我编写了一个名为 FluentIL (www.fluentil.org) 的开源库来简化它。我在这里用过。

[]s

于 2012-03-27T13:43:40.633 回答
0

C# Reflection IL - 了解如何复制值

问题是关于克隆,因此源对象的类型与目标对象的类型相同(据我所知,源对象和目标对象可以有不同的类型),但这仍然值得分析。

于 2012-03-18T21:04:22.610 回答