重构消除Lambda表达式中的重复

这两种方法表现出重复:

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

我怎样才能重构以消除这种重复?

更新:哎呀,我忽略了一个重要的观点。 FooEditDto是FooDto的子类。

如果FooEditDto是FooDto的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> EditDtoSelector() { return f => new FooEditDto(f.PropertyA,f.PropertyB,f.PropertyC) { PropertyD = f.PropertyD, PropertyE = f.PropertyE }; } 

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

您可以编写一个使用reflection的方法(请加上我!)来计算特定类型的所有属性,并构建一个委托(使用Reflection.Emit)将属性从该类型复制到另一个类型。 然后使用匿名类型确保您只需要构建一次复制委托,因此速度很快。 您的方法将如下所示:

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

这里的细微差别:

  • MagicCopier是generics类型,Copy是一种通用方法,因此您可以显式指定“目标”类型,但隐式指定“源”类型。
  • 它使用投影初始化程序从用于初始化匿名类型的表达式中推断属性的名称

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

编辑:使用MemberInitExpression,我们可以使用表达式树完成所有操作,这使得它比CodeDOM更容易。 今晚会试一试……

编辑:完成,它实际上是非常简单的代码。 这是class级:

 ///  /// 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. ///  public static class PropertyCopy where TTarget : class, new() { ///  /// Copies all readable properties from the source to a new instance /// of TTarget. ///  public static TTarget CopyFrom(TSource source) where TSource : class { return PropertyCopier.Copy(source); } ///  /// 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. ///  private static class PropertyCopier where TSource : class { private static readonly Func 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 BuildCopier() { ParameterExpression sourceParameter = Expression.Parameter(typeof(TSource), "source"); var bindings = new List(); 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>(initializer, sourceParameter).Compile(); } } 

并称之为:

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

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

请注意您选择的任何方法的性能影响。 反思本身就比较慢。 但是,您也可以使用reflection来发射IL,一旦发出IL,就会像您所写的一样快地运行。 您还可以生成表达式树并将其转换为已编译的委托。 这些技术有点复杂,所以你必须权衡权衡。

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

 public static Expression> GetSelector(Expression> f) { return f; } /* ... */ var expr = GetSelector(f => new{f.PropertyA,f.PropertyB,f.PropertyC});