【问题标题】:Don't the expression need to be faster for getting a property value than the reflection?获取属性值的表达式不需要比反射更快吗?
【发布时间】:2014-10-19 10:46:24
【问题描述】:

我知道使用表达式获取属性值比使用反射更快,我想将列表转换为数据表,我已经使用了它们,

反射经过时间:36 ms

表达式经过时间:2350 ms

我想知道我在那里做错了什么?

我试过下面的代码:

    public class Foo
    {
        public long IntCode { get; set; }
        public string Name { get; set; }
        public string SurName { get; set; }
        public int Age { get; set; }
    }
    class Program
    {
        static void Main(string[] args)
        {
            var r = new Random();
            var foos = new List<Foo>();
            var sw = new Stopwatch();
            sw.Start();
            for (int i = 0; i < 10000; i++)
            {
                foos.Add(new Foo { IntCode = r.Next(), Name = Guid.NewGuid().ToString(), SurName = Guid.NewGuid().ToString(), Age = r.Next() });
            }
            sw.Stop();

            Console.WriteLine("Elapsed Time For Creating : {0}", sw.ElapsedMilliseconds);
            sw.Restart();
            ConvertWithReflection(foos, "IntCode", "Name", "Age");
            sw.Stop();
            Console.WriteLine("Elapsed Time For Converting : {0}", sw.ElapsedMilliseconds);

            sw.Restart();
            ConvertWithExpression(foos, "IntCode", "Name", "Age");
            sw.Stop();
            Console.WriteLine("Elapsed Time For Converting : {0}", sw.ElapsedMilliseconds);
            Console.ReadLine();
        }

        public static object GetValueGetter<T>(object item,string propertyName)
        {
            var arg = Expression.Parameter(item.GetType(), "x");
            Expression expr = Expression.Property(arg, propertyName);
            var unaryExpression = Expression.Convert(expr, typeof(object));
            var propertyResolver = Expression.Lambda<Func<T, object>>(unaryExpression, arg).Compile();
            var value = propertyResolver((T)item);
            return value;
        }

        public static void ConvertWithReflection<T>(IEnumerable<T> list, params string[] columnNames)
        {
            var t = list.ToList();
            if (!t.Any()) return;
            var dataTable = new DataTable();
            dataTable.Columns.Add("IntCode");
            dataTable.Columns.Add("Name");
            dataTable.Columns.Add("SurName");
            dataTable.Columns.Add("Age");
            foreach (var item in t)
            {
                var dr = dataTable.NewRow();
                for (int i = 0; i < dataTable.Columns.Count; i++)
                {
                    var el = columnNames.ElementAtOrDefault(i);
                    if (el == null)
                    {
                        dr[i] = DBNull.Value;
                    }
                    else
                    {
                        var property =  item.GetType().GetProperty(el);
                        dr[i] = property.GetValue(item, null);
                    }
                }
                dataTable.Rows.Add(dr);
            }
        }

        public static void ConvertWithExpression<T>(IEnumerable<T> list, params string[] columnNames)
        {
            var t = list.ToList();
            if (!t.Any()) return;
            var dataTable = new DataTable();
            dataTable.Columns.Add("IntCode");
            dataTable.Columns.Add("Name");
            dataTable.Columns.Add("SurName");
            dataTable.Columns.Add("Age");
            foreach (var item in t)
            {
                var dr = dataTable.NewRow();
                for (var i = 0; i < dataTable.Columns.Count; i++)
                {
                    var el = columnNames.ElementAtOrDefault(i);
                    if (el == null)
                    {
                        dr[i] = DBNull.Value;
                    }
                    else
                    {
                        dr[i] = GetValueGetter<T>(item, el);
                    }
                }
                dataTable.Rows.Add(dr);
            }
        }
    }

【问题讨论】:

  • 是什么让您认为表达式应该更快?表达式使用反射...可以更快的是生成和缓存委托并重用它,但是您的代码每次都在重建和编译委托,这需要很多时间。
  • 我明白了,我不知道怎么缓存,能不能做个示例?

标签: c# performance reflection expression


【解决方案1】:

您不是在比较苹果和苹果:您的表达式代码在每次迭代中构造和编译一个表达式,在每次迭代中产生相当多的一次性活动。另一方面,反射代码使用了 CLR 的设计者对系统进行的所有优化,只执行必要的操作。

本质上,您是在比较表达式的准备时间 + 工作时间与反思的工作时间。这不是在操作重复 10,000 次的情况下使用表达式的预期方式:您需要预先准备和编译 lambda,将它们存储在某种缓存中,然后在每次迭代时根据需要快速检索它们。实现某种缓存会使您的比较结果更平:

public static object GetValueGetter<T>(object item, string propertyName, IDictionary<string,Func<T,object>> cache) {
    Func<T, object> propertyResolver;
    if (!cache.TryGetValue(propertyName, out propertyResolver)) {
        var arg = Expression.Parameter(item.GetType(), "x");
        Expression expr = Expression.Property(arg, propertyName);
        var unaryExpression = Expression.Convert(expr, typeof (object));
        propertyResolver = Expression.Lambda<Func<T, object>>(unaryExpression, arg).Compile();
        cache.Add(propertyName, propertyResolver);
    }
    return propertyResolver((T)item);
}

调用看起来像这样:

var cache = new Dictionary<string,Func<T,object>>();
foreach (var item in t) {
    var dr = dataTable.NewRow();
    for (var i = 0; i < dataTable.Columns.Count; i++) {
        var el = columnNames.ElementAtOrDefault(i);
        if (el == null) {
            dr[i] = DBNull.Value;
        } else {
            dr[i] = GetValueGetter<T>(item, el, cache);
        }
    }
    dataTable.Rows.Add(dr);
}

现在准备成本分散在 10,000 次调用中,反射成为三种方法中速度较慢的方法:

Elapsed Time For Creating : 29
Elapsed Time For Converting : 84  <-- Reflection
Elapsed Time For Converting : 53  <-- Expressions

【讨论】:

  • 非常感谢,我明白了,每个属性都需要缓存。
【解决方案2】:

您正在重复编译表达式。编译一次,速度会更快。

如果每次编译表达式的速度更快,那么运行时会自动完成。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-12-31
    • 1970-01-01
    • 2011-10-02
    • 1970-01-01
    • 2011-09-03
    相关资源
    最近更新 更多