代码之家  ›  专栏  ›  技术社区  ›  DirtyNative

如何用反射改进代码

  •  0
  • DirtyNative  · 技术社区  · 8 年前

    我已经编写了一个方法,它简单地将一个对象的所有给定属性复制到具有相同类型的另一个对象。之所以使用此方法,是因为我不想在类具有100+时手动定义要复制的属性(希望这种情况永远不会发生,但如果…)。

        /// <summary>
        /// Copies the values of the given parameters from source to target
        /// Important Info: Works only with Properties, not with Fields
        /// </summary>
        /// <typeparam name="T">The Classtype</typeparam>
        /// <param name="target">The object the values are copied to</param>
        /// <param name="source">The object the values come from</param>
        /// <param name="properties">The Array containing the names of properties which shall be copied</param>
        private static void CopyParams<T>(T target, T source, params string[] properties)
        {
            foreach (var property in properties)
            {
                target.GetType().GetProperty(property)?.SetValue(target, source.GetType().GetProperty(property)?.GetValue(source));
            }
        }
    

    但是因为它在循环中使用反射,所以速度非常慢。使用1000.000个对象和2个属性,最多需要2秒。如果我手动操作,需要36毫秒。有没有办法提高性能?

    编辑1

    由于一些人要求对象的代码,这里是:

    public class TestModel
    {
        public string Name { get; set; }
    
        public int Value { get; set; }
    
        public void GetValues(TestModel m)
        {
            Name = m.Name;
            Value = m.Value;
        }
    }
    

    代码的调用方式如下:

        private static void PerformanceTestReflection(int count)
        {
            var models = new List<TestModel>();
            var copies = new List<TestModel>();
    
            for (int i = 0; i < count; i++)
            {
                models.Add(new TestModel() { Name = "original", Value = 10 });
                copies.Add(new TestModel() { Name = "copy", Value = 20 });
            }
    
            Stopwatch sw = Stopwatch.StartNew();
    
            for (int i = 0; i < count; i++)
            {
                CopyParams(models[i], copies[i], nameof(TestModel.Name), nameof(TestModel.Value));
            }
    
            Console.WriteLine($"Time for Reflection with {count} Models: {sw.ElapsedMilliseconds} ms - {sw.ElapsedTicks} ticks");
        }
    
    1 回复  |  直到 8 年前
        1
  •  2
  •   xanatos    8 年前

    有可能对表达式树做些什么。。。有三种方法:为每个属性创建一个表达式,为每个属性组合创建一个表达式,为每个包含大 foreach (var property in properties) switch (property) { case "Prop1": target.Prop1 = source.Prop1; break; ... } } (在这种情况下 target.Prop1 = source.Prop1 不使用反射)。

    我会做第一个最简单的。

    public static class Tools
    {
        public static void CopyFrom<T>(this T target, T source, params string[] properties)
        {
            ToolsImpl<T>.CopyFrom(target, source, properties);
        }
    
        private static class ToolsImpl<T>
        {
            private static readonly ConcurrentDictionary<string, Action<T, T>> delegates = new ConcurrentDictionary<string, Action<T, T>>();
    
            public static void CopyFrom(T target, T source, string[] properties)
            {
                foreach (var property in properties)
                {
                    Action<T, T> del;
    
                    if (!delegates.TryGetValue(property, out del))
                    {
                        var t2 = Expression.Parameter(typeof(T), "t");
                        var s2 = Expression.Parameter(typeof(T), "s");
    
                        var prop = typeof(T).GetProperty(property);
    
                        // The ?. in the source: skip missing properties
    
                        if (prop == null)
                        {
                            continue;
                        }
    
                        Expression<Action<T, T>> exp = Expression.Lambda<Action<T, T>>(Expression.Assign(Expression.Property(t2, prop), Expression.Property(s2, prop)), t2, s2);
                        del = exp.Compile();
                        delegates.TryAdd(property, del);
                    }
    
                    del(target, source);
                }
    
            }
        }
    }
    

    这里是“one property==one expression tree”的代码。为“一组属性=一个表达式树”执行此操作稍微复杂一些,因为您需要一个 string[] . 第三个更复杂(你有 for / foreach 它不在表达式树中,因此必须构建,您有 switch 那总是一种痛苦)

    和(编译的)表达式树一样,第一次运行的速度和狗一样慢,然后它变得更快。

    附录

    只是出于好奇,第三种方式(模拟 对于 循环+ 转换 ) :

    public static class Tools
    {
        public static void CopyFrom<T>(this T target, T source, params string[] properties)
        {
            ToolsImpl<T>.CopyTo(source, target, properties);
        }
    
        private static class ToolsImpl<T>
        {
            public static readonly Action<T, T, string[]> CopyTo;
    
            static ToolsImpl()
            {
                var source = Expression.Parameter(typeof(T), "s");
                var target = Expression.Parameter(typeof(T), "t");
                var properties = Expression.Parameter(typeof(string[]), "properties");
    
                // indexer of the for cycle
                var i = Expression.Variable(typeof(int), "i");
    
                // case "prop1": target.prop1 = source.prop1
                var cases = typeof(T)
                    .GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(x => x.CanRead && x.CanWrite && x.GetIndexParameters().Length == 0)
                    .Select(x => Expression.SwitchCase(Expression.Assign(Expression.Property(target, x), Expression.Property(source, x)), Expression.Constant(x.Name)));
    
                // switch properties[i]:
                var sw = Expression.Switch(typeof(void), Expression.ArrayAccess(properties, i), null, null, cases);
    
                var lblForBegin = Expression.Label(typeof(void), "for begin");
                var lblForCheck = Expression.Label(typeof(void), "for check");
    
                // we simulate a for (int i = 0; i < properties.Length; ++i
                var body = Expression.Block(new[] { i },
                    new Expression[]
                    {
                        Expression.Assign(i, Expression.Constant(0)), // ix = 0
                        Expression.Goto(lblForCheck), // goto lblForCheck
                        Expression.Label(lblForBegin), // :lblForBegin
                        sw, // switch ()
                        Expression.PreIncrementAssign(i), // ++i
                        Expression.Label(lblForCheck), // :lblForCheck
                        Expression.IfThen(Expression.LessThan(i, Expression.ArrayLength(properties)), Expression.Goto(lblForBegin)), // if ix < properties.Length goto lblForBegin
                    });
    
                var exp = Expression.Lambda<Action<T, T, string[]>>(body, source, target, properties);
                CopyTo = exp.Compile();
            }
        }
    }