dotnet 5 静态字段和属性的反射获取 没有想象中那么伤性能

在最近在做 WPF 框架开发的时候,看到了在 WPF 的 StaticExtension 里面,有部分逻辑采用了反射的方法去获取静态字段和静态属性。此时我第一个反应就是这部分逻辑的性能有锅,于是尝试了进行加上缓存来优化。但是在使用了 Benchmark 进行性能测试的时候发现了,其实加上了缓存的性能反而更差,也就是说在 dotnet 5 里面的反射获取静态字段和属性的性能没有想象的伤性能

本文并非说反射获取静态字段和属性不伤性能,而是指在本文约定的情况下,没有那么伤性能。本文完全依靠性能测试来说明

换句话说,不要在外面说德熙这个逗比说反射获取静态字段和属性不伤性能哈。如果要说句话,还请加上一大堆条件

在原本的 WPF 框架里面有以下的逻辑,用来获取静态字段或属性

        private bool GetFieldOrPropertyValue(Type type, string name, out object value)
        {
            FieldInfo field = null;
            Type temp = type;

            do
            {
                field = temp.GetField(name, BindingFlags.Public | BindingFlags.Static);
                if (field != null)
                {
                    value = field.GetValue(null);
                    return true;
                }

                temp = temp.BaseType;
            } while (temp != null);


            PropertyInfo prop = null;
            temp = type;

            do
            {
                prop = temp.GetProperty(name, BindingFlags.Public | BindingFlags.Static);
                if (prop != null)
                {
                    value = prop.GetValue(null, null);
                    return true;
                }

                temp = temp.BaseType;
            } while (temp != null);

            value = null;
            return false;
        }

此时我期望不需要每次都通过 GetField 或 GetProperty 方法去获取字段或属性的 FieldInfo 或 PropertyInfo 对象,再通过这些对象去获取实际的值,甚至我都想要作出缓存,通过 Func<object> 的方法返回静态属性或字段

但是实际测试发现了其实尝试省去 通过 GetField 或 GetProperty 方法去获取字段或属性的 FieldInfo 或 PropertyInfo 对象,将 FieldInfo 或 PropertyInfo 对象缓存起来,甚至通过 Func<object> 的方法返回静态属性或字段的性能,其实都和没有提升,甚至还因为构建字典的 Key 而下降,我采用了两个方法进行性能优化,分别是缓存起来字段或属性的 FieldInfo 或 PropertyInfo 对象,用来减少 GetField 或 GetProperty 方法的调用。另一个就是通过 Func<object> 的方法返回静态属性或字段

通过缓存 FieldInfo 或 PropertyInfo 对象的方法被我称为 WithCache 的方法。而通过 Func<object> 的方法返回静态属性或字段的方法被我称为 GetFieldWithField 或 GetPropertyWithProperty 方法

通过接口 IFieldOrPropertyValueGetter 可以定义不同的方式获取静态字段或属性,如下面代码

        interface IFieldOrPropertyValueGetter
        {
            object GetObject();
        }

        class DelegateValueGetter : IFieldOrPropertyValueGetter
        {
            public DelegateValueGetter(Func<object> getter)
            {
                _getter = getter;
            }

            public object GetObject()
            {
                return _getter();
            }

            private readonly Func<object> _getter;
        }

        class FieldValueGetter : IFieldOrPropertyValueGetter
        {
            public FieldValueGetter(FieldInfo fieldInfo)
            {
                _fieldInfo = fieldInfo;
            }

            public object GetObject()
            {
                return _fieldInfo.GetValue(null);
            }

            private readonly FieldInfo _fieldInfo;
        }

        class PropertyValueGetter : IFieldOrPropertyValueGetter
        {
            public PropertyValueGetter(PropertyInfo propertyInfo)
            {
                _propertyInfo = propertyInfo;
            }

            public object GetObject()
            {
                return _propertyInfo.GetValue(null, null);
            }

            private readonly PropertyInfo _propertyInfo;
        }

而根据 Type 和对应的字段或属性名可以获取静态的字段或属性的方法,就需要参数中包含了两个参数,一个是 Type 一个 Name 代表字段或属性名。构建出的字典如下

Dictionary<(Type type, string name), IFieldOrPropertyValueGetter>

实现的通过缓存获取静态的字段或属性方法如下

        private bool GetFieldOrPropertyValueWithCache(Type type, string name, out object value,
            Dictionary<(Type type, string name), IFieldOrPropertyValueGetter> creatorDictionary)
        {
            if (!creatorDictionary.TryGetValue((type, name), out var creator))
            {
                creator = GetCreator(type, name);
                creatorDictionary.Add((type, name), creator);
            }

            if (creator != null)
            {
                value = creator.GetObject();
                return true;
            }
            else
            {
                value = null;
                return false;
            }
        }

在没有从缓存字典里面获取到的时候,将会调用 GetCreator 方法获取创建器。当然了上面的命名是有锅的,应该是获取器才对,而不是 creator 创建器

性能测试的代码如下

    public static class Foo
    {
        public static string Property { get; } = "Property";

        public static string Field = "Field";
    }

    public class Program
    {
        static void Main(string[] args)
        {
            var program = new Program();
            program.GetFieldWithField(new object[10]);

            BenchmarkRunner.Run<Program>();
        }


        [Benchmark()]
        [ArgumentsSource(nameof(GetTime))]
        public object GetFieldWithCache(object[] getObjectTimeList)
        {
            var creatorDictionary = new Dictionary<(Type type, string name), IFieldOrPropertyValueGetter>();
            for (var i = 0; i < getObjectTimeList.Length; i++)
            {
                GetFieldOrPropertyValueWithCache(typeof(Foo), "Field", out var value, creatorDictionary);

                getObjectTimeList[i] = value;
            }

            return getObjectTimeList;
        }

        [Benchmark()]
        [ArgumentsSource(nameof(GetTime))]
        public object GetPropertyWithCache(object[] getObjectTimeList)
        {
            var creatorDictionary = new Dictionary<(Type type, string name), IFieldOrPropertyValueGetter>();
            for (var i = 0; i < getObjectTimeList.Length; i++)
            {
                GetFieldOrPropertyValueWithCache(typeof(Foo), "Property", out var value, creatorDictionary);

                getObjectTimeList[i] = value;
            }

            return getObjectTimeList;
        }


        [Benchmark()]
        [ArgumentsSource(nameof(GetTime))]
        public object GetPropertyWithProperty(object[] getObjectTimeList)
        {
            var creatorDictionary = new Dictionary<(Type type, string name), IFieldOrPropertyValueGetter>()
            {
                {(typeof(Foo), "Property"), new DelegateValueGetter(() => Foo.Property)}
            };
            for (var i = 0; i < getObjectTimeList.Length; i++)
            {
                GetFieldOrPropertyValueWithCache(typeof(Foo), "Property", out var value, creatorDictionary);

                getObjectTimeList[i] = value;
            }

            return getObjectTimeList;
        }

        [Benchmark()]
        [ArgumentsSource(nameof(GetTime))]
        public object GetFieldWithField(object[] getObjectTimeList)
        {
            var creatorDictionary = new Dictionary<(Type type, string name), IFieldOrPropertyValueGetter>()
            {
                {(typeof(Foo), "Field"), new DelegateValueGetter(() => Foo.Field)}
            };
            for (var i = 0; i < getObjectTimeList.Length; i++)
            {
                GetFieldOrPropertyValueWithCache(typeof(Foo), "Field", out var value, creatorDictionary);

                getObjectTimeList[i] = value;
            }

            return getObjectTimeList;
        }

        [Benchmark()]
        [ArgumentsSource(nameof(GetTime))]
        public object GetFieldWithOriginMethod(object[] getObjectTimeList)
        {
            for (var i = 0; i < getObjectTimeList.Length; i++)
            {
                GetFieldOrPropertyValue(typeof(Foo), "Field", out var value);

                getObjectTimeList[i] = value;
            }

            return getObjectTimeList;
        }

        [Benchmark(Baseline = true)]
        [ArgumentsSource(nameof(GetTime))]
        public object GetPropertyWithOriginMethod(object[] getObjectTimeList)
        {
            for (var i = 0; i < getObjectTimeList.Length; i++)
            {
                GetFieldOrPropertyValue(typeof(Foo), "Property", out var value);

                getObjectTimeList[i] = value;
            }

            return getObjectTimeList;
        }

        public IEnumerable<object[]> GetTime()
        {
            foreach (var count in GetTimeInner())
            {
                yield return new object[] {new object[count]};
            }

            IEnumerable<int> GetTimeInner()
            {
                yield return 1;
                yield return 2;
                yield return 10;
                yield return 100;
                yield return 1000;
            }
        }

        interface IFieldOrPropertyValueGetter
        {
            object GetObject();
        }

        class DelegateValueGetter : IFieldOrPropertyValueGetter
        {
            public DelegateValueGetter(Func<object> getter)
            {
                _getter = getter;
            }

            public object GetObject()
            {
                return _getter();
            }

            private readonly Func<object> _getter;
        }

        class FieldValueGetter : IFieldOrPropertyValueGetter
        {
            public FieldValueGetter(FieldInfo fieldInfo)
            {
                _fieldInfo = fieldInfo;
            }

            public object GetObject()
            {
                return _fieldInfo.GetValue(null);
            }

            private readonly FieldInfo _fieldInfo;
        }

        class PropertyValueGetter : IFieldOrPropertyValueGetter
        {
            public PropertyValueGetter(PropertyInfo propertyInfo)
            {
                _propertyInfo = propertyInfo;
            }

            public object GetObject()
            {
                return _propertyInfo.GetValue(null, null);
            }

            private readonly PropertyInfo _propertyInfo;
        }

        private bool GetFieldOrPropertyValueWithCache(Type type, string name, out object value,
            Dictionary<(Type type, string name), IFieldOrPropertyValueGetter> creatorDictionary)
        {
            if (!creatorDictionary.TryGetValue((type, name), out var creator))
            {
                creator = GetCreator(type, name);
                creatorDictionary.Add((type, name), creator);
            }

            if (creator != null)
            {
                value = creator.GetObject();
                return true;
            }
            else
            {
                value = null;
                return false;
            }
        }

        private IFieldOrPropertyValueGetter GetCreator(Type type, string name)
        {
            FieldInfo field = null;
            Type temp = type;

            do
            {
                field = temp.GetField(name, BindingFlags.Public | BindingFlags.Static);
                if (field != null)
                {
                    return new FieldValueGetter(field);
                }

                temp = temp.BaseType;
            } while (temp != null);


            PropertyInfo prop = null;
            temp = type;

            do
            {
                prop = temp.GetProperty(name, BindingFlags.Public | BindingFlags.Static);
                if (prop != null)
                {
                    return new PropertyValueGetter(prop);
                }

                temp = temp.BaseType;
            } while (temp != null);

            return null;
        }

        private bool GetFieldOrPropertyValue(Type type, string name, out object value)
        {
            FieldInfo field = null;
            Type temp = type;

            do
            {
                field = temp.GetField(name, BindingFlags.Public | BindingFlags.Static);
                if (field != null)
                {
                    value = field.GetValue(null);
                    return true;
                }

                temp = temp.BaseType;
            } while (temp != null);


            PropertyInfo prop = null;
            temp = type;

            do
            {
                prop = temp.GetProperty(name, BindingFlags.Public | BindingFlags.Static);
                if (prop != null)
                {
                    value = prop.GetValue(null, null);
                    return true;
                }

                temp = temp.BaseType;
            } while (temp != null);

            value = null;
            return false;
        }
    }

性能测试如下,大家也可以自己跑一下

Method执行次数MeanErrorStdDevMedianRatio
GetFieldWithCache1000184.28 ns3.760 ns8.937 ns181.24 ns0.87
GetPropertyWithCache1000333.72 ns3.558 ns3.154 ns333.82 ns1.52
GetPropertyWithProperty1000157.95 ns2.842 ns2.519 ns157.88 ns0.72
GetFieldWithField1000151.52 ns2.469 ns2.189 ns151.14 ns0.69
GetFieldWithOriginMethod100074.31 ns0.988 ns0.876 ns74.07 ns0.34
GetPropertyWithOriginMethod1000219.91 ns4.371 ns6.128 ns217.90 ns1.00
GetFieldWithCache100199.02 ns5.517 ns16.007 ns199.47 ns0.94
GetPropertyWithCache100385.85 ns8.923 ns26.030 ns389.29 ns1.77
GetPropertyWithProperty100156.59 ns2.109 ns1.973 ns156.23 ns0.71
GetFieldWithField100153.75 ns3.155 ns3.240 ns152.58 ns0.70
GetFieldWithOriginMethod10077.35 ns1.539 ns2.107 ns77.70 ns0.35
GetPropertyWithOriginMethod100228.61 ns6.463 ns18.544 ns219.22 ns1.06
GetFieldWithCache10199.89 ns5.461 ns16.102 ns201.19 ns0.94
GetPropertyWithCache10344.20 ns6.926 ns15.633 ns339.23 ns1.62
GetPropertyWithProperty10155.89 ns2.431 ns2.274 ns155.34 ns0.71
GetFieldWithField10148.79 ns1.975 ns1.847 ns148.61 ns0.67
GetFieldWithOriginMethod1073.58 ns0.759 ns0.710 ns73.63 ns0.33
GetPropertyWithOriginMethod10216.26 ns1.804 ns1.507 ns216.62 ns0.98
GetFieldWithCache1175.15 ns1.928 ns1.610 ns175.07 ns0.80
GetPropertyWithCache1335.90 ns3.287 ns3.074 ns335.84 ns1.52
GetPropertyWithProperty1166.51 ns4.381 ns12.570 ns161.50 ns0.81
GetFieldWithField1150.82 ns2.063 ns1.723 ns151.13 ns0.69
GetFieldWithOriginMethod173.73 ns0.593 ns0.555 ns73.68 ns0.33
GetPropertyWithOriginMethod1216.67 ns1.991 ns1.765 ns216.38 ns0.98
GetFieldWithCache2175.28 ns3.448 ns4.105 ns174.05 ns0.79
GetPropertyWithCache2357.45 ns7.190 ns16.521 ns352.56 ns1.63
GetPropertyWithProperty2167.96 ns3.619 ns10.385 ns166.05 ns0.79
GetFieldWithField2166.61 ns5.091 ns14.851 ns163.09 ns0.74
GetFieldWithOriginMethod278.34 ns1.626 ns4.559 ns77.55 ns0.38
GetPropertyWithOriginMethod2230.22 ns4.547 ns11.153 ns228.05 ns1.06

上面测试中的 GetFieldWithCache 和 GetPropertyWithCache 分别表示通过缓存的方法,减少调用 GetField 或 GetProperty 方法去获取字段或属性的 FieldInfo 或 PropertyInfo 对象,但依然使用 GetValue 的方法反射读取属性

而 GetPropertyWithProperty 和 GetFieldWithField 方法则是创建委托的方式,返回的就是具体的静态字段或属性

上面代码中性能最好的 GetFieldWithOriginMethod 其实就是 WPF 中原本读取静态字段的方法,里面完全用到反射,没有加上缓存。而 GetPropertyWithOriginMethod 就是对应的 WPF 中原本读取静态属性的方法,可以看到反射读取静态速度的性能其实还是很好的

为什么性能测试的结果是这样的,原因是创建缓存以及创建缓存的 Key 的时间比预期的长很多,因此导致了其实不加缓存的性能更好

上面测试能否说明反射获取静态属性的性能比不过反射获取静态字段的值。因此根据上面的测试,可以看到反射获取静态属性 GetPropertyWithOriginMethod 的时间是 230.22 ns 左右。而反射获取静态字段的时间是 78.34 ns 左右。其实不能,原因是在 WPF 源代码里面是先尝试读取静态字段,在读取不到的时候,才去读取静态属性,因此静态属性读取速度会比静态字段慢

因为没有发现当前我的加上缓存的优化能比原先的方法性能更好,因此我就不敢将代码提到 WPF 官方

代码放在 github 欢迎小伙伴访问

知识共享许可协议
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。欢迎转载、使用、重新发布,但务必保留文章署名林德熙(包含链接:http://blog.csdn.net/lindexi_gd ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系。

lindexi_gd CSDN认证博客专家 C# WPF UWP 微软最具价值专家
我是微软Windows应用开发方向的最具价值专家,欢迎访问我博客blog.lindexi.com里面有大量WPF和UWP博客
已标记关键词 清除标记
相关推荐
----------Database-------------- 1.DataTable帮助类(DataTableHelper.cs) 2.Access数据库文件操作辅助类(JetAccessUtil.cs) 3.常用的Access数据库Sql操作辅助类库(OleDbHelper.cs) 4.根据各种不同数据库生成不同【分页语句的辅助类】 PagerHelper(PageHelper.cs) 5.查询条件组合辅助类(SearchCondition.cs) 6.查询信息实体类(SearchInfo.cs) 7.类型(例如:int,string,double)转换(SmartDataReader.cs) 8.Sql命令操作函数(可用于安装程序的时候数据库脚本执行)(SqlScriptHelper.cs) ----------Device-------------- 1.声音播放辅助类(AudioHelper.cs) 2.摄像头操作辅助类,包括开启、关闭、抓图、设置等功能(Camera.cs) 3.提供用于操作【剪切板】的方法(ClipboardHelper.cs) 4.获取电脑信息(Computer.cs) 5.提供用户硬件唯一信息的辅助类(FingerprintHelper.cs) 6.读取指定盘符的硬盘序列号(HardwareInfoHelper.cs) 7.提供访问键盘当前状态的属性(KeyboardHelper.cs) 8.全局键盘钩子。这可以用来在全球范围内捕捉键盘输入。(KeyboardHook.cs) 9.模拟鼠标点击(MouseHelper.cs) 10.全局鼠标钩子。这可以用来在全球范围内捕获鼠标输入。(MouseHook.cs) 11.MP3文件播放操作辅助类(MP3Helper.cs) ----------Encrypt-------------- 1.基于Base64的加密编码(Base64Util.cs) 2.字符串的加密/解密(EncodeHelper.cs) 3.MD5各种长度加密字符、验证MD5等操作辅助类(MD5Util.cs) 4.QQ的EncryptUtil(QQEncryptUtil.cs) 5.非对称加密验证辅助类(RSASecurityHelper.cs) ----------File-------------- 1.用于获取或设置Web.config/*.exe.config节点数据的辅助类(AppConfig.cs) 2.CSV文件和DataTable对象转换辅助类(CSVHelper.cs) 3.DatabaseInfo 的摘要说明。(DatabaseInfo.cs) 4.常用的目录操作辅助类(DirectoryUtil.cs) 5.Excel操作辅助类(无需VBA引用)(ExcelHelper.cs) 6.利用VBA对象,导出DataView到一个Excel文档的Excel辅助类(Export2Excel.cs) 7.关联文件(ExtensionAttachUtil.cs) 8.注册文件关联的辅助类(FileAssociationsHelper.cs) 9.打开、保存文件对话框操作辅助类(FileDialogHelper.cs) 10.常用的文件操作辅助类FileUtil(FileUtil.cs) 11.INI文件操作辅助类(INIFileUtil.cs) 12.独立存储操作辅助类(IsolatedStorageHelper.cs) 13.序列号操作辅助类(Serializer.cs) 14.获取一个对象,它提供用于访问经常引用的目录的属性。(SpecialDirectories.cs) 15.简单的Word操作对象(WordCombineUtil.cs) 16.这个类提供了一些实用的方法来转换XML和对象。(XmlConvertor.cs) 17.XML操作类(XmlHelper.cs) ----------Format-------------- 1.参数验证的通用验证程序。(ArgumentValidation.cs) 2.这个类提供了实用方法的字节数组和图像之间的转换。(ByteImageConvertor.cs) 3.byte字节数组操作辅助类(BytesTools.cs) 4.处理数据类型转换,数制转换、编码转换相关的类(ConvertHelper.cs) 5.CRC校验辅助类(CRCUtils.cs) 6.枚举操作公共类(EnumHelper.cs) 7.身份证操作辅助类(IDCardHelper.cs) 8.检测字符编码的类(IdentifyEncoding.cs) 9.RGB颜色操作辅助类(MyColors.cs) 10.日期操作类(MyDateTime.cs) 11.转换人民币大小金额辅助类(RMBUtil.cs) 12.常用的字符串常量(StringConstants.cs) 13.简要说明TextHelper。(StringUtil.cs) 14.获取文字首字拼写,随机发生器,按指定概率随机执行操作(Util.cs) 15.各种输入格式验证辅助类(ValidateUtil.cs) ----------Network-------------- 1.Cookie操作辅助类(CookieManger.cs) 2.FTP操作辅助类(FTPHelper.cs) 3.HTML操作类(HttpHelper.cs) 4.网页抓取帮助(HttpWebRequestHelper.cs) 5.Net(NetworkUtil.cs) 6.IE代理设置辅助类(ProxyHelper.cs) ----------Winform-------------- 1.跨线程的控件安全访问方式(CallCtrlWithThreadSafety.cs) 2.CheckBoxList(CheckBoxListUtil.cs) 3.窗口管理类(ChildWinManagement.cs) 4.由马丁·米勒http://msdn.microsoft.com/en-us/library/ms996492.aspx提供一个简单的方法打印工作的一个RichTextBox一个帮手(ExRichTextBoxPrintHelper.cs) 5.显示,隐藏或关闭动画形式。(FormAnimator.cs) 6.对窗体进行冻结、解冻操作辅助类(FreezeWindowUtil.cs) 7.窗体全屏操作辅助类(FullScreenHelper.cs) 8.GDI操作辅助类(GDI.cs) 9.提供静态方法来读取这两个文件夹和文件的系统图标。(IconReaderHelper.cs) 10.图片对象比较、缩放、缩略图、水印、压缩、转换、编码等操作辅助类(ImageHelper.cs) 11.输入法帮助,全角 转换为半角(ImeHelper.cs) 12.Winform提示框 的摘要说明。(MessageUtil.cs) 13.包含互操作方法调用的应用程序使用。(NativeMethods.cs) 14.托盘图标辅助类(NotifyIconHelper.cs) 15.打印机类(POSPrinter.cs) 16.图片、光标、图标、位图等资源操作辅助类(ResourceHelper.cs) 17.RTF字符格式辅助类(RTFUtility.cs) 18.串口开发辅助类(SerialPortUtil.cs) 19.设置文本属性提供一个ToolStripStatusLabel(SafeToolStripLabel.cs) 20.只运行一个实例及系统自动启动辅助类(StartupHelper.cs) 21.Web页面预览效果图片抓取辅助类(WebPageCapture.cs) 22.供Asp.Net直接调用的包装类(WebPreview.cs) 23.计算机重启、关电源、注销、关闭显示器辅助类(WindowsExitHelper.cs) ----------NONONONO-------------- 1.全局统一的缓存类(Cache.cs) 2.常用显示日期时间、农历、生肖的日历类(CCalendar.cs,DateTimeHelper.cs) 3.国农历年处理类(ChineseCalendar.cs) 4.正则表达式辅助类(CRegex.cs) 5.CString 的摘要说明。(CString.cs) 6.CText文本内容的类库(CText.cs) 7.初始化语言环境(CultureInfoUtil.cs) 8.压缩文本、字节或者文件的压缩辅助类(GZipUtil.cs) 9.Log4Net日志记录辅助类(LogHelper.cs) 10.文字符串转换为拼音或者拼音首字母的辅助类(PinYinUtil.cs) 11.随机汉字辅助类(RandomChinese.cs) 12.反射操作辅助类,如获取或设置字段属性的值等反射信息。(ReflectionUtil.cs) 13.注册表操作辅助类(RegistryHelper.cs) 14.用于验证码图片识别的类(UnCodebase.cs) 15.将原始字串转换为unicode,格式为\u.\u.( UnicodeHelper.cs)
©️2020 CSDN 皮肤主题: 终极编程指南 设计师:CSDN官方博客 返回首页