微信公众号登录不上seo门户网
微信公众号登录不上,seo门户网,做网站和推广的公司哪家好,爱客crm软件下载前言实体类的动态生成#xff08;一#xff09;由于采用字典的方式来保存属性变更值的底层设计思想#xff0c;导致了性能问题#xff0c;虽然.NET的字典实现已经很高效了#xff0c;但相对于直接读写字段的方式而言依然有巨大的性能差距#xff0c;同时也会导致对属性的… 前言实体类的动态生成一由于采用字典的方式来保存属性变更值的底层设计思想导致了性能问题虽然.NET的字典实现已经很高效了但相对于直接读写字段的方式而言依然有巨大的性能差距同时也会导致对属性的读写过程中产生不必要的装箱和拆箱。那么这次我们就来彻底解决这个问题同时还要解决“哪些属性发生过变更”、“获取变更的属性集”这些功能特性所以我们先把接口定义出来以便后续问题讲解。/* 源码位于 Zongsoft.CoreLibary 项目的 Zongsoft.Data 命名空间中 *//// summary 表示数据实体的接口。/summarypublic interface IEntity{/// summary/// 判断指定的属性或任意属性是否被变更过。/// /summary/// param namenames指定要判断的属性名数组如果为空(null)或空数组则表示判断任意属性。/param/// returns/// para如果指定的paramref namenames/参数有值当只有参数中指定的属性发生过更改则返回真(True)否则返回假(False)/para/// para如果指定的paramref namenames/参数为空(null)或空数组当实体中任意属性发生过更改则返回真(True)否则返回假(False)。/para/// /returnsbool HasChanges(params string[] names);/// summary/// 获取实体中发生过变更的属性集。/// /summary/// returns如果实体没有属性发生过变更则返回空(null)否则返回被变更过的属性键值对。/returnsIDictionarystring, object GetChanges();/// summary/// 尝试获取指定名称的属性变更后的值。/// /summary/// param namename指定要获取的属性名。/param/// param namevalue输出参数指定属性名对应的变更后的值。/param/// returns如果指定名称的属性是存在的并且发生过变更则返回真(True)否则返回假(False)。/returns/// remarks注意即使指定名称的属性是存在的但只要其值未被更改过也会返回假(False)。/remarksbool TryGetValue(string name, out object value);/// summary/// 尝试设置指定名称的属性值。/// /summary/// param namename指定要设置的属性名。/param/// param namevalue指定要设置的属性值。/param/// returns如果指定名称的属性是存在的并且可写入则返回真(True)否则返回假(False)。/returnsbool TrySetValue(string name, object value);}设计思想根本要点是取消用字典来保存属性值回归到字段方式只有这样才能确保性能关键问题是如何在写入字段值的时候标记对应的属性发生过变更的呢应用布隆过滤器(Bloom Filter)算法的思路来处理这个应用场景是一个完美的解决方案因为布隆过滤器的空间效率和查询效率极高而它的缺点在此恰好可以针对性的优化掉。将每个属性映射到一个整型数byte/ushort/uint/ulong的某个比特位(bit)如果发生过变更则将该 bit 置为1只要确保属性与二进制位顺序是确定的即可算法复杂度是O(1)常量并且比特位操作的效率也是极高的。实现示范有了算法我们写一个简单范例来感受下public class Person : IEntity{#region 静态字段private static readonly string[] __NAMES__ new string[] { Name, Gender, Birthdate };private static readonly Dictionarystring, PropertyTokenPerson __TOKENS__ new Dictionarystring, PropertyTokenPerson(){{ Name, new PropertyTokenPerson(0, target target._name, (target, value) target.Name (string) value) },{ Gender, new PropertyTokenPerson(1, target target._gender, (target, value) target.Gender (Gender?) value) },{ Birthdate, new PropertyTokenPerson(2, target target._birthdate, (target, value) target.Birthdate (DateTime) value) },};#endregion#region 标记变量private byte _MASK_;#endregion#region 成员字段private string _name;private bool? _gender;private DateTime _birthdate;#endregion#region 公共属性public string Name {get _name;set{_name value;_MASK_ | 1;}}public bool? Gender {get _gender;set{_gender value;_MASK_ | 2;}}public DateTime Birthdate {get _birthdate;set{_birthdate value;_MASK_ | 4;}}#endregion#region 接口实现public bool HasChanges(string[] names){PropertyTokenPerson property;if(names null || names.Length 0)return _MASK_ ! 0;for(var i 0; i names.Length; i){if(__TOKENS__.TryGetValue(names[i], out property) (_MASK_ property.Ordinal 1) 1)return true;}return false;}public IDictionarystring, object GetChanges(){if(_MASK_ 0)return null;var dictionary new Dictionarystring, object(__NAMES__.Length);for(int i 0; i __NAMES__.Length; i){if((_MASK_ i 1) 1)dictionary[__NAMES__[i]] __TOKENS__[__NAMES__[i]].Getter(this);}return dictionary;}public bool TryGetValue(string name, out object value){value null;if(__TOKENS__.TryGetValue(name, out var property) (_MASK_ property.Ordinal 1) 1){value property.Getter(this);return true;}return false;}public bool TrySetValue(string name, object value){if(__TOKENS__.TryGetValue(name, out var property)){property.Setter(this, value);return true;}return false;}#endregion}// 辅助结构public struct PropertyTokenT{public PropertyToken(int ordinal, FuncT, object getter, ActionT, object setter){this.Ordinal ordinal;this.Getter getter;this.Setter setter;}public readonly int Ordinal;public readonly FuncT, object Getter;public readonly ActionT, object Setter;}上面实现代码主要有以下几个要点属性设置器中除了对字段赋值外多了一个位或赋值操作这是一句非常低成本的代码需要一个额外的整型数的实例字段 _MASK_ 来标记对应更改属性序号分别增加 __NAMES__ 和 __TOKENS__ 两个静态只读变量来保存实体类的元数据以便更高效的实现 IEntity 接口方法。根据代码可分析出其理论执行性能与原生实现基本一致内存消耗只多了一个字节如果可写属性数量小于9由于 __NAMES__ 和 __TOKENS__ 是静态变量因此不占用实例空间理论上该方案的整体效率非常高。性能对比上面我们从代码角度简单分析了下整个方案的性能和消耗那么实际情况到底怎样呢跑个分呗https://github.com/Zongsoft/Zongsoft.CoreLibrary/tree/feature-data/samples/Zongsoft.Samples.Entities。下面是某次在我的老旧台式机CPU:Intel i5-34703.2GHz | RAM:8GB | Win10 | .NET 4.6上生成100万个实例的截图“Native Object: 295”表示原生实现版即简单的读写字段的运行时长单位毫秒下同“Data Entity: 295”为本案的运行时长通常本方案比原生方案要慢10毫秒左右偶尔能跑平属于运行环境抖动可忽略“Data Entity(TrySet): 835”为本方案中 TrySet(...) 方法的运行时长由于 TrySet(...) 方法内部需要进行字典查询所以有性能损耗亦属正常在百万量级跑到这个时长说明性能也是很不错的如果切换到 .NET Core 2.1 的话得益于基础类库的性能改善还能再享受一波性能红利。综上所述该方案付出极少的内存成本获得了与原生简单属性访问基本一致的性能同时还提供了属性变更跟踪等新功能即高效完成了 Zongsoft.Data.IEntity 接口中定义的那些重要功能特性为后续业务开发提供了有力的基础支撑。实现完善上面的实现范例代码并没有实现 INotifyPropertyChanged 接口下面补充完善下实现该接口后的属性定义public class Person : IEntity, INotifyPropertyChanged{// 事件声明public event PropertyChangedEventHandler PropertyChanged;public string Name {get _name;set{if(_name value)return;_name value;_MASK_ | 1;this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));}}}如上属性的设置器中的做了一个新旧值的比对判断和对 PropertyChanged 事件激发其他代码没有变化。另外我们使用的是 byte 类型的 _MASK_ 的标记变量来保存属性的更改状态如果当实体的属性数量超过 8 个就需要根据具体数量换成相应的 UInt16,UInt32,UInt64 类型但如果超过 64 就需要采用 byte[] 了当然必须要变动下相关代码假设以下实体类有 100 个属性注意仅例举了第一个 Property1 和最后一个 Property100属性public class MyEntity : IEntity{#region 标记变量private readonly byte[] _MASK_;#endregionpublic Person(){_MASK_ new byte[13]; // 13 Math.Ceiling(100 / 8)}public object Property1 {get _property1;set{_property1 value;_MASKS_[0] | 1; // _MASK_[0 / 8] | (byte)Math.Pow(2, 0 % 8);}}public object Property100 {get _property100;set{_property100 value;_MASKS_[12] | 8; // _MASK_[99 / 8] | (byte)Math.Pow(2, 99 % 8);}}}变化内容为先根据当前属性的顺序号来确定到对应的标记数组的下标然后再确定对应的掩码值。当然也别忘了调整 Zongsoft.Data.IEntity 接口中各方法的实现。public class MyEntity : IEntity{public bool HasChanges(params string[] names){PropertyTokenUserEntity property;if(names null || names.Length 0){for(int i 0; i _MASK_.Length; i){if(_MASK_[i] ! 0)return true;}return false;}for(var i 0; i names.Length; i){if(__TOKENS__.TryGetValue(names[i], out property) (_MASK_[property.Ordinal / 8] (property.Ordinal % 8) 1) 1)return true;}return false;}public IDictionarystring, object GetChanges(){var dictionary new Dictionarystring, object(__NAMES__.Length);for(int i 0; i __NAMES__.Length; i){if((_MASK_[i / 8] (i % 8) 1) 1)dictionary[__NAMES__[i]] __TOKENS__[__NAMES__[i]].Getter(this);}return dictionary.Count 0 ? null : dictionary;}public bool TryGet(string name, out object value){value null;if(__TOKENS__.TryGetValue(name, out var property) (_MASK_[property.Ordinal / 8] (property.Ordinal % 8) 1) 1){value property.Getter(this);return true;}return false;}public bool TrySetValue(string name, object value){/* 相对之前版本没有变化 *//* No changes relative to previous versions */}}代码变化部分比较简单只有掩码处理部分需要调整。新问题有了这些实现范式定义个实体基类并在基类中完成主要功能即可推广应用了但是这里有个掩码类型和处理方式无法通用化实现的问题如果要把这部分代码交由子类来实现的话那么代码复用度会大打折扣甚至完全失去复用的意义。为展示这个问题的艰难在 https://github.com/Zongsoft/Zongsoft.CoreLibrary/blob/feature-data/tests/Entities.cs 源文件中写了属性数量不等的几个实体类Person、Customer、Employee、SpecialEmployee采用继承方式进行复用性验证可清晰看到实现的非常冗长繁琐对实现者的细节把控要求很高、实现上非常容易出错更致命的是复用度还极差。并且当实体类需要进行属性增减是非常麻烦的需要仔细调整原有代码结构中掩码的映射位置这对于代码维护无意是场恶梦。新办法解决办法其实很简单正是本文的标题——“动态生成”彻底解放实现者并确保实现的正确性。业务方不再定义具体的实体类而是定义实体接口即可实体类将由实体生成器来动态生成。我们依然“从场景出发”先来看看业务层的使用。public interface IPerson : IEntity{string Name { get; set; }bool? Gender { get; set; }DateTime Birthdate { get; set; }}public interface IEmployee : IPerson{byte Status { get; set; }decimal Salary { get; set; }}var person Entity.BuildIPerson();var employee Entity.BuildIEmployee();总结至此终于得到了一个兼顾性能与功能并易于使用且无需繁琐的手动实现的最终方案虽然刚开始看起来是一个多么平常又简单的任务。那么接下来我们该怎么实现这个动态生成器呢最终它能性能无损的被实现出来吗请关注我们的公众号Zongsoft留言讨论。相关文章实体类的动态生成一原文地址http://zongsoft.github.io/blog/zh-cn/zongsoft/entity-dynamic-generation-2/.NET社区新闻深度好文欢迎访问公众号文章汇总 http://www.csharpkit.com
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/90099.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!