c# - 表达式获取属性值不需要比反射更快吗?

标签 c# performance reflection expression

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

反射耗时: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);
            }
        }
    }

最佳答案

您并不是在比较苹果与苹果:您的表达式代码在每次迭代中构造并编译表达式,从而在每次迭代中产生相当数量的废弃事件。另一方面,反射代码使用 CLR 设计者已放入系统中的所有优化,仅执行必要的操作。

本质上,您正在比较表达的准备时间+工作时间与反射(reflection)的工作时间。当操作重复 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

关于c# - 表达式获取属性值不需要比反射更快吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26449701/

相关文章:

c# - 为什么微软不为 stringbuilder 重载 += 运算符?

algorithm - 从 4 个不同序列的元素中找到总和 x

java - 如何更改旧单元测试的私有(private)抽象父字段

java - Java 中的什么在虚拟内存中使用 400M,我如何降低该使用量?

c# - 如何在 C# 中从字符串创建泛型类?

c# - 如何安全访问对象的属性

c# - 用正则表达式换行

c# - 实体'不包含带有 1 个参数的构造函数

c# - 如何从 Internet 获取 DateTime(外部资源 - 不是来自服务器)

performance - 如何在 MATLAB 中编写矢量化函数