日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

实体类的动态生成(二)

發(fā)布時間:2023/12/4 编程问答 39 豆豆
生活随笔 收集整理的這篇文章主要介紹了 实体类的动态生成(二) 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

前言

實(shí)體類的動態(tài)生成(一)由于采用字典的方式來保存屬性變更值的底層設(shè)計思想,導(dǎo)致了性能問題,雖然.NET的字典實(shí)現(xiàn)已經(jīng)很高效了,但相對于直接讀寫字段的方式而言依然有巨大的性能差距,同時也會導(dǎo)致對屬性的讀寫過程中產(chǎn)生不必要的裝箱和拆箱。

那么這次我們就來徹底解決這個問題,同時還要解決“哪些屬性發(fā)生過變”、“獲取變更的屬性集”這些功能特性,所以我們先把接口定義出來,以便后續(xù)問題講解。

/* 源碼位于 Zongsoft.CoreLibary 項目的 Zongsoft.Data 命名空間中 */

/// <summary> 表示數(shù)據(jù)實(shí)體的接口。</summary>
public interface IEntity
{/// <summary>/// 判斷指定的屬性或任意屬性是否被變更過。/// </summary>/// <param name="names">指定要判斷的屬性名數(shù)組,如果為空(null)或空數(shù)組則表示判斷任意屬性。</param>/// <returns>/// <para>如果指定的<paramref name="names"/>參數(shù)有值,當(dāng)只有參數(shù)中指定的屬性發(fā)生過更改則返回真(True),否則返回假(False);</para>/// <para>如果指定的<paramref name="names"/>參數(shù)為空(null)或空數(shù)組,當(dāng)實(shí)體中任意屬性發(fā)生過更改則返回真(True),否則返回假(False)。</para>/// </returns>bool HasChanges(params string[] names);/// <summary>/// 獲取實(shí)體中發(fā)生過變更的屬性集。/// </summary>/// <returns>如果實(shí)體沒有屬性發(fā)生過變更,則返回空(null),否則返回被變更過的屬性鍵值對。</returns>IDictionary<string, object> GetChanges();/// <summary>/// 嘗試獲取指定名稱的屬性變更后的值。/// </summary>/// <param name="name">指定要獲取的屬性名。</param>/// <param name="value">輸出參數(shù),指定屬性名對應(yīng)的變更后的值。</param>/// <returns>如果指定名稱的屬性是存在的并且發(fā)生過變更,則返回真(True),否則返回假(False)。</returns>/// <remarks>注意:即使指定名稱的屬性是存在的,但只要其值未被更改過,也會返回假(False)。</remarks>bool TryGetValue(string name, out object value);/// <summary>/// 嘗試設(shè)置指定名稱的屬性值。/// </summary>/// <param name="name">指定要設(shè)置的屬性名。</param>/// <param name="value">指定要設(shè)置的屬性值。</param>/// <returns>如果指定名稱的屬性是存在的并且可寫入,則返回真(True),否則返回假(False)。</returns>bool TrySetValue(string name, object value);
}

設(shè)計思想

根本要點(diǎn)是取消用字典來保存屬性值回歸到字段方式,只有這樣才能確保性能,關(guān)鍵問題是如何在寫入字段值的時候,標(biāo)記對應(yīng)的屬性發(fā)生過變更的呢?應(yīng)用布隆過濾器(Bloom Filter)算法的思路來處理這個應(yīng)用場景是一個完美的解決方案,因為布隆過濾器的空間效率和查詢效率極高,而它的缺點(diǎn)在此恰好可以針對性的優(yōu)化掉。

將每個屬性映射到一個整型數(shù)(byte/ushort/uint/ulong)的某個比特位(bit),如果發(fā)生過變更則將該?bit?置為1,只要確保屬性與二進(jìn)制位順序是確定的即可,算法復(fù)雜度是O(1)常量,并且比特位操作的效率也是極高的。

實(shí)現(xiàn)示范

有了算法,我們寫一個簡單范例來感受下:

public class Person : IEntity
{#region 靜態(tài)字段private static readonly string[] __NAMES__ = new string[] { "Name", "Gender", "Birthdate" };private static readonly Dictionary<string, PropertyToken<Person>> __TOKENS__ = new Dictionary<string, PropertyToken<Person>>(){{ "Name", new PropertyToken<Person>(0, target => target._name, (target, value) => target.Name = (string) value) },{ "Gender", new PropertyToken<Person>(1, target => target._gender, (target, value) => target.Gender = (Gender?) value) },{ "Birthdate", new PropertyToken<Person>(2, target => target._birthdate, (target, value) => target.Birthdate = (DateTime) value) },};#endregion#region 標(biāo)記變量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 接口實(shí)現(xiàn)public bool HasChanges(string[] names){PropertyToken<Person> 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 IDictionary<string, object> GetChanges(){if(_MASK_ == 0)return null;var dictionary = new Dictionary<string, 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
}

// 輔助結(jié)構(gòu)
public struct PropertyToken<T>
{public PropertyToken(int ordinal, Func<T, object> getter, Action<T, object> setter){this.Ordinal = ordinal;this.Getter = getter;this.Setter = setter;}public readonly int Ordinal;public readonly Func<T, object> Getter;public readonly Action<T, object> Setter;
}

上面實(shí)現(xiàn)代碼,主要有以下幾個要點(diǎn):

  • 屬性設(shè)置器中除了對字段賦值外,多了一個位或賦值操作(這是一句非常低成本的代碼);

  • 需要一個額外的整型數(shù)的實(shí)例字段?_MASK_?,來標(biāo)記對應(yīng)更改屬性序號;

  • 分別增加?__NAMES__?和?__TOKENS__?兩個靜態(tài)只讀變量,來保存實(shí)體類的元數(shù)據(jù),以便更高效的實(shí)現(xiàn)?IEntity 接口方法。

  • 根據(jù)代碼可分析出其理論執(zhí)行性能與原生實(shí)現(xiàn)基本一致,內(nèi)存消耗只多了一個字節(jié)(如果可寫屬性數(shù)量小于9),由于?__NAMES__?和?__TOKENS__?是靜態(tài)變量,因此不占用實(shí)例空間,理論上該方案的整體效率非常高。

    性能對比

    上面我們從代碼角度簡單分析了下整個方案的性能和消耗,那么實(shí)際情況到底怎樣呢?跑個分唄(https://github.com/Zongsoft/Zongsoft.CoreLibrary/tree/feature-data/samples/Zongsoft.Samples.Entities

    下面是某次在我的老舊臺式機(jī)(CPU:Intel?i5-3470@3.2GHz?| RAM:8GB?|?Win10?|?.NET 4.6)上生成100萬個實(shí)例的截圖:

    • “Native Object:?295”表示原生實(shí)現(xiàn)版(即簡單的讀寫字段)的運(yùn)行時長(單位:毫秒,下同);

    • “Data Entity:?295”為本案的運(yùn)行時長,通常本方案比原生方案要慢10毫秒左右,偶爾能跑平(屬于運(yùn)行環(huán)境抖動,可忽略);

    • “Data Entity(TrySet):?835”為本方案中?TrySet(...)?方法的運(yùn)行時長,由于?TrySet(...)?方法內(nèi)部需要進(jìn)行字典查詢所以有性能損耗亦屬正常,在百萬量級跑到這個時長說明性能也是很不錯的,如果切換到 .NET Core 2.1 的話,得益于基礎(chǔ)類庫的性能改善,還能再享受一波性能紅利。

    綜上所述,該方案付出極少的內(nèi)存成本獲得了與原生簡單屬性訪問基本一致的性能,同時還提供了屬性變更跟蹤等新功能(即高效完成了 Zongsoft.Data.IEntity 接口中定義的那些重要功能特性),為后續(xù)業(yè)務(wù)開發(fā)提供了有力的基礎(chǔ)支撐。

    實(shí)現(xiàn)完善

    上面的實(shí)現(xiàn)范例代碼并沒有實(shí)現(xiàn)?INotifyPropertyChanged?接口,下面補(bǔ)充完善下實(shí)現(xiàn)該接口后的屬性定義:

    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)));}}
    }

    如上,屬性的設(shè)置器中的做了一個新舊值的比對判斷和對?PropertyChanged?事件激發(fā),其他代碼沒有變化。

    另外,我們使用的是 byte 類型的?_MASK_?的標(biāo)記變量來保存屬性的更改狀態(tài),如果當(dāng)實(shí)體的屬性數(shù)量超過 8 個,就需要根據(jù)具體數(shù)量換成相應(yīng)的?UInt16,UInt32,UInt64?類型,但如果超過 64 就需要采用?byte[]?了,當(dāng)然必須要變動下相關(guān)代碼,假設(shè)以下實(shí)體類有?100?個屬性(注意僅例舉了第一個?Property1?和最后一個?Property100屬性):

    public class MyEntity : IEntity
    {#region 標(biāo)記變量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);}}
    }

    變化內(nèi)容為先根據(jù)當(dāng)前屬性的順序號來確定到對應(yīng)的標(biāo)記數(shù)組的下標(biāo),然后再確定對應(yīng)的掩碼值。當(dāng)然,也別忘了調(diào)整 Zongsoft.Data.IEntity 接口中各方法的實(shí)現(xiàn)。

    public class MyEntity : IEntity
    {public bool HasChanges(params string[] names){PropertyToken<UserEntity> 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 IDictionary<string, object> GetChanges(){var dictionary = new Dictionary<string, 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 */}
    }

    代碼變化部分比較簡單,只有掩碼處理部分需要調(diào)整。

    新問題

    有了這些實(shí)現(xiàn)范式,定義個實(shí)體基類并在基類中完成主要功能即可推廣應(yīng)用了,但是,這里有個掩碼類型和處理方式無法通用化實(shí)現(xiàn)的問題,如果要把這部分代碼交由子類來實(shí)現(xiàn)的話,那么代碼復(fù)用度會大打折扣甚至完全失去復(fù)用的意義。

    為展示這個問題的艱難,在 https://github.com/Zongsoft/Zongsoft.CoreLibrary/blob/feature-data/tests/Entities.cs 源文件中,寫了屬性數(shù)量不等的幾個實(shí)體類(Person、Customer、Employee、SpecialEmployee),采用繼承方式進(jìn)行復(fù)用性驗證,可清晰看到實(shí)現(xiàn)的非常冗長繁瑣,對實(shí)現(xiàn)者的細(xì)節(jié)把控要求很高、實(shí)現(xiàn)上非常容易出錯,更致命的是復(fù)用度還極差。并且當(dāng)實(shí)體類需要進(jìn)行屬性增減,是非常麻煩的,需要仔細(xì)調(diào)整原有代碼結(jié)構(gòu)中掩碼的映射位置,這對于代碼維護(hù)無意是場惡夢。

    新辦法

    解決辦法其實(shí)很簡單,正是本文的標(biāo)題——“動態(tài)生成”,徹底解放實(shí)現(xiàn)者并確保實(shí)現(xiàn)的正確性。業(yè)務(wù)方不再定義具體的實(shí)體類,而是定義實(shí)體接口即可,實(shí)體類將由實(shí)體生成器來動態(tài)生成。我們依然“從場景出發(fā)”,先來看看業(yè)務(wù)層的使用。

    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.Build<IPerson>();
    var employee = Entity.Build<IEmployee>();

    總結(jié)

    至此,終于得到了一個兼顧性能與功能并易于使用且無需繁瑣的手動實(shí)現(xiàn)的最終方案,雖然剛開始看起來是一個多么平常又簡單的任務(wù)。那么接下來我們該怎么實(shí)現(xiàn)這個動態(tài)生成器呢?最終它能性能無損的被實(shí)現(xiàn)出來嗎?請關(guān)注我們的公眾號(Zongsoft)留言討論。

    相關(guān)文章:

    • 實(shí)體類的動態(tài)生成(一)

    原文地址:http://zongsoft.github.io/blog/zh-cn/zongsoft/entity-dynamic-generation-2/

    .NET社區(qū)新聞,深度好文,歡迎訪問公眾號文章匯總 http://www.csharpkit.com

    總結(jié)

    以上是生活随笔為你收集整理的实体类的动态生成(二)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。