【问题标题】:Refactor To Eliminate Repetition In Lambda Expression重构以消除 Lambda 表达式中的重复
【发布时间】:2015-05-26 06:52:31
【问题描述】:

这两种方法表现出重复:

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 的子类。

标签: c# refactoring lambda


【解决方案1】:

好吧,我有一个非常可怕的方法可以做到。

您可以编写一个使用反射的方法(请耐心等待!)来计算特定类型的所有属性,并构建一个委托(使用 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 是泛型方法,因此您可以显式指定“目标”类型,但隐式指定“源”类型。
  • 它使用投影初始化器从用于初始化匿名类型的表达式中推断出属性的名称

我不确定它是否真的值得,但这是一个非常有趣的想法......无论如何我可能不得不实现它:)

编辑:使用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" });

【讨论】:

  • 您也可以使用 PropertyInfos 的 getter/setter 来反映类型并通过匹配属性名称来复制值
  • 是的,但是每次都使用反射,这非常缓慢。鉴于您可以缓存已编译的表达式树以复制值,这将更加有效。鉴于库代码只需要编写一次,还不如正确完成:)
  • 感谢您的帖子...我可以看到我可能会在某个时候使用它。
  • 是否可以返回 Expression> 代替?
  • 我想知道您是否可以在这里查看有关您的解决方案的类似问题:stackoverflow.com/questions/1093449/…
【解决方案2】:

如果 FooEditDtoFooDto 的子类并且您不需要 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
    };
}

【讨论】:

    【解决方案3】:

    重复出现在名称中,但 C# 不知道一个类中的 PropertyA 与另一个类中的 PropertyA 相关联。您必须明确地建立连接。你做的方式很好。如果您有足够的这些,您可能会考虑使用反射来编写一个可以为任何一对类执行此操作的方法。

    请注意您选择的任何方法对性能的影响。反射本身较慢。但是,您也可以使用反射来发出 IL,一旦发出,它的运行速度与您编写的一样快。您还可以生成表达式树并将其转换为已编译的委托。这些技术有些复杂,因此您必须权衡取舍。

    【讨论】:

      【解决方案4】:

      您可以让调用者返回他们自己的匿名类型对象,只包含他们需要的属性:

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

      【讨论】:

        猜你喜欢
        • 2013-01-23
        • 1970-01-01
        • 2011-01-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多