передача значений одного свойства объекта другому - PullRequest
4 голосов
/ 19 марта 2012

Прежде всего, я знаю о 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 и делегатов с поздним связыванием быстрее и имеет более высокую производительность. Но не было больше объяснения или какой-либо ссылки. Можете ли вы помочь мне понять способы ускорить этот код? или вы можете предложить мне несколько ссылок о Emit, ILGenerator и делегатах с поздним связыванием , пожалуйста? Или что-то, что вы думаете, поможет мне в теме?

ЗАВЕРШЕНО Q:

Я понял и многому научился из ответа @ 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 ]

5 голосов
/ 19 марта 2012

Вы могли бы использовать Reflection.Emit, чтобы сделать это, но обычно гораздо проще использовать Expression с, и это дает вам в основном ту же производительность.Имейте в виду, что выигрыш в производительности есть только в том случае, если вы кешируете скомпилированный код, например, в 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);
}

Если у вас это есть, пишите свой универсальный методis simpple:

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> и привести делегат к нужному типу после извлечения его из кеша.

3 голосов
/ 19 марта 2012

Вы можете рассмотреть только получение свойств (по имени), которые соответствуют цели. Это значительно упростит ваш код.

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 );
     }
}
0 голосов
/ 27 марта 2012

Я написал сообщение в блоге о том, как это сделать (только на португальском языке, но вы могли прочитать код)

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

0 голосов
/ 19 марта 2012

C # Reflection IL - Понимание того, как копируются значения

Вопрос о клонировании , поэтому тип исходного объекта совпадает с типом объектацелевой объект (хотя, как я понимаю, у вас могут быть разные типы в источнике и цели), но все же это может стоить проанализировать.

...