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

Эти два метода показывают повторение:

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.

Ответы [ 4 ]

2 голосов
/ 21 октября 2008

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

Вы могли бы написать метод, который использовал отражение (терпите меня!), Чтобы проработать все свойства для определенного типа, и создал делегат (используя 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 - универсальный метод, так что вы можете явно указать тип «target», но неявно указать тип «source».
  • Он использует инициализатор проекции, чтобы вывести имена свойств из выражений, используемых для инициализации анонимного типа

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

РЕДАКТИРОВАТЬ: С MemberInitExpression мы можем сделать все это с деревом выражений, что делает его намного проще, чем 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" });
1 голос
/ 21 октября 2008

Если 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
    };
}
0 голосов
/ 21 октября 2008

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

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});
0 голосов
/ 21 октября 2008

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...