Рефакторинг для устранения повторения в лямбда-выражении

StackOverflow https://stackoverflow.com/questions/222511

  •  03-07-2019
  •  | 
  •  

Вопрос

Эти два метода демонстрируют повторение:

public static Expression<Func<Foo, FooEditDto>> EditDtoSelector()
{
    return f => new FooEditDto
    {
        PropertyA = f.PropertyA,
        PropertyB = f.PropertyB,
        PropertyC = f.PropertyC,
        PropertyD = f.PropertyD,
        PropertyE = f.PropertyE
    };
}

public static Expression<Func<Foo, FooListDto>> ListDtoSelector()
{
    return f => new FooDto
    {
        PropertyA = f.PropertyA,
        PropertyB = f.PropertyB,
        PropertyC = f.PropertyC
    };
}

Как я могу провести рефакторинг, чтобы устранить это повторение?

ОБНОВЛЯТЬ:Упс, я забыл упомянуть важный момент.FooEditDto — это подкласс FooDto.

Это было полезно?

Решение

Если FooEditDto является подклассом FooDto и вам не нужны MemberInitExpressions, используйте конструктор:

class FooDto
 { public FooDto(Bar a, Bar b, Bar c) 
    { PropertyA = a;
      PropertyB = b;
      PropertyC = c;
    }
   public Bar PropertyA {get;set;}
   public Bar PropertyB {get;set;}
   public Bar PropertyC {get;set;}
 }

class FooEditDto : FooDto
 { public FooEditDto(Bar a, Bar b, Bar c) : base(a,b,c)
   public Bar PropertyD {get;set;}
   public Bar PropertyE {get;set;}
 }

public static Expression<Func<Foo, FooEditDto>> EditDtoSelector()
{
    return f => new FooEditDto(f.PropertyA,f.PropertyB,f.PropertyC)
    {
        PropertyD = f.PropertyD,
        PropertyE = f.PropertyE
    };
}

Другие советы

Ну, у меня есть действительно ужасно как вы могли бы это сделать.

Вы можете написать метод, который использует отражение (потерпите!) для обработки всех свойств для определенного типа и создает делегат (используя Reflection.Emit) для копирования свойств из этого типа в другой.Затем используйте анонимный тип, чтобы убедиться, что вам нужно создать копирующий делегат только один раз, чтобы это было быстро.Тогда ваш метод будет выглядеть так:

public static Expression<Func<Foo, FooEditDto>> EditDtoSelector()
{
    return f => MagicCopier<FooEditDto>.Copy(new { 
        f.PropertyA, f.PropertyB, f.PropertyC, f.PropertyD, f.PropertyC
    });
}

Нюансы здесь:

  • MagicCopier — это универсальный тип, а Copy — это универсальный метод, позволяющий явно указать «целевой» тип, но неявно указать «исходный» тип.
  • Он использует инициализатор проекции для вывода имен свойств из выражений, используемых для инициализации анонимного типа.

Я не уверен, стоит ли оно того, но это довольно забавная идея...Возможно, мне все равно придется это реализовать :)

РЕДАКТИРОВАТЬ:С Членинитвыражение мы могли бы сделать все это с помощью дерева выражений, что делает его намного проще, чем CodeDOM.Сегодня вечером попробую...

РЕДАКТИРОВАТЬ:Готово, и на самом деле это довольно простой код.Вот класс:

/// <summary>
/// Generic class which copies to its target type from a source
/// type specified in the Copy method. The types are specified
/// separately to take advantage of type inference on generic
/// method arguments.
/// </summary>
public static class PropertyCopy<TTarget> where TTarget : class, new()
{
    /// <summary>
    /// Copies all readable properties from the source to a new instance
    /// of TTarget.
    /// </summary>
    public static TTarget CopyFrom<TSource>(TSource source) where TSource : class
    {
        return PropertyCopier<TSource>.Copy(source);
    }

    /// <summary>
    /// Static class to efficiently store the compiled delegate which can
    /// do the copying. We need a bit of work to ensure that exceptions are
    /// appropriately propagated, as the exception is generated at type initialization
    /// time, but we wish it to be thrown as an ArgumentException.
    /// </summary>
    private static class PropertyCopier<TSource> where TSource : class
    {
        private static readonly Func<TSource, TTarget> copier;
        private static readonly Exception initializationException;

        internal static TTarget Copy(TSource source)
        {
            if (initializationException != null)
            {
                throw initializationException;
            }
            if (source == null)
            {
                throw new ArgumentNullException("source");
            }
            return copier(source);
        }

        static PropertyCopier()
        {
            try
            {
                copier = BuildCopier();
                initializationException = null;
            }
            catch (Exception e)
            {
                copier = null;
                initializationException = e;
            }
        }

        private static Func<TSource, TTarget> BuildCopier()
        {
            ParameterExpression sourceParameter = Expression.Parameter(typeof(TSource), "source");
            var bindings = new List<MemberBinding>();
            foreach (PropertyInfo sourceProperty in typeof(TSource).GetProperties())
            {
                if (!sourceProperty.CanRead)
                {
                    continue;
                }
                PropertyInfo targetProperty = typeof(TTarget).GetProperty(sourceProperty.Name);
                if (targetProperty == null)
                {
                    throw new ArgumentException("Property " + sourceProperty.Name 
                        + " is not present and accessible in " + typeof(TTarget).FullName);
                }
                if (!targetProperty.CanWrite)
                {
                    throw new ArgumentException("Property " + sourceProperty.Name 
                        + " is not writable in " + typeof(TTarget).FullName);
                }
                if (!targetProperty.PropertyType.IsAssignableFrom(sourceProperty.PropertyType))
                {
                    throw new ArgumentException("Property " + sourceProperty.Name
                        + " has an incompatible type in " + typeof(TTarget).FullName);
                }
                bindings.Add(Expression.Bind(targetProperty, Expression.Property(sourceParameter, sourceProperty)));
            }
            Expression initializer = Expression.MemberInit(Expression.New(typeof(TTarget)), bindings);
            return Expression.Lambda<Func<TSource,TTarget>>(initializer, sourceParameter).Compile();
        }
    }

И называя это:

TargetType target = PropertyCopy<TargetType>.CopyFrom(new { First="Foo", Second="Bar" });

Повторяется в именах, но C # не знает, что PropertyA в одном классе связана с PropertyA в другом. Вы должны сделать соединение явно. То, как вы это сделали, работает нормально. Если вам этого достаточно, вы можете рассмотреть возможность использования отражения для написания одного метода, который мог бы сделать это для любой пары классов.

Обращайте внимание на последствия для производительности любого выбранного вами метода. Отражение само по себе медленнее. Тем не менее, вы также можете использовать рефлексию для генерации IL, который после его генерации будет работать так же быстро, как вы написали. Вы также можете создать дерево выражений и преобразовать его в скомпилированный делегат. Эти методы несколько сложны, поэтому вы должны взвесить компромиссы.

Вы можете разрешить вызывающей стороне возвращать свой собственный объект анонимного типа только с необходимыми им свойствами:

public static Expression<Func<Foo,T>> 
                             GetSelector<T>(Expression<Func<Foo,T>> f)
 { return f;
 }

/* ... */
var expr = GetSelector(f => new{f.PropertyA,f.PropertyB,f.PropertyC});
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top