代码之家  ›  专栏  ›  技术社区  ›  Tim Scott

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

  •  6
  • Tim Scott  · 技术社区  · 16 年前

    这两种方法表现出重复性:

    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 回复  |  直到 10 年前
        1
  •  2
  •   Jon Skeet    16 年前

    嗯,我有一个 真的很可怕 你能做到的方式。

    你可以编写一个方法,使用反射(请耐心听我说!)来计算特定类型的所有属性,并构建一个委托(使用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" });
    
        2
  •  1
  •   Mark Cidade    16 年前

    如果 FooEditDto 是一个子类 FooDto 如果你不需要MemberInitExpression,请使用构造函数:

    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
  •  0
  •   Neil    16 年前

    重复出现在名称中,但C#不知道一个类中的PropertyA与另一个类的PropertyA相连。你必须明确地建立连接。你做这件事的方式很好。如果你已经受够了这些,你可能会考虑使用反射来编写一个方法,可以为任何一对类做到这一点。

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

        4
  •  0
  •   Mark Cidade    16 年前

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

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