2008-10-21 36 views
6

這兩種方法表現出重複:重構爲消除重複在Lambda表達式

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的一個子類。

+0

請參閱我添加的說明。 FooEditDto是FooDto的一個子類。 – 2008-10-21 19:29:47

回答

1

如果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 
    }; 
} 
0

重複是在名稱中,但C#不知道一個類中的PropertyA與另一個類中的PropertyA連接。你必須明確地建立連接。你做的方式很好。如果你有足夠的這些,你可以考慮使用反射來寫一種方法,可以爲任何類對做這個。

請注意所選方法的性能影響。反思本身較慢。然而,你也可以使用反射來發射IL,一旦它被髮射出來,就可以像你寫的那樣快速地運行。您也可以生成表達式樹並將其轉換爲已編譯的委託。這些技術有點複雜,所以你必須權衡權衡。

2

那麼,我有一個真的很可怕你可以做到這一點。

您可以編寫一個方法,它使用反射(與我同在!)來計算特定類型的所有屬性,並構建一個委託(使用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是一個通用型和複製是一種通用的方法,這樣可以明確指定「目標」類型,但含蓄指定「源」類型。
  • 它使用投影初始推斷從使用的表達式屬性的名稱來初始化匿名類型

我不知道它是否真的值得,但它是一個相當有趣的想法...我可能不得不實施它:)

編輯:與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" }); 
+0

你也可以只反映類型並通過匹配屬性名稱來複制值,使用PropertyInfos中的getters/setters – 2008-10-21 17:06:18

0

你可以讓來電者返回自己的匿名類型的對象,具有唯一屬性,他們需要:

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