成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專(zhuān)欄INFORMATION COLUMN

實(shí)體類(lèi)的動(dòng)態(tài)生成(二)

mochixuan / 2345人閱讀

摘要:源碼位于項(xiàng)目的命名空間中表示數(shù)據(jù)實(shí)體的接口。獲取實(shí)體中發(fā)生過(guò)變更的屬性集。新辦法解決辦法其實(shí)很簡(jiǎn)單,正是本文的標(biāo)題動(dòng)態(tài)生成,徹底解放實(shí)現(xiàn)者并確保實(shí)現(xiàn)的正確性。業(yè)務(wù)方不再定義具體的實(shí)體類(lèi),而是定義實(shí)體接口即可,實(shí)體類(lèi)將由實(shí)體生成器來(lái)動(dòng)態(tài)生成。

前言

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

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

/* 源碼位于 Zongsoft.CoreLibary 項(xiàng)目的 Zongsoft.Data 命名空間中 */

///  表示數(shù)據(jù)實(shí)體的接口。
public interface IEntity
{
 ? ?/// 
 ? ?/// 判斷指定的屬性或任意屬性是否被變更過(guò)。
 ? ?/// 
 ? ?/// 指定要判斷的屬性名數(shù)組,如果為空(null)或空數(shù)組則表示判斷任意屬性。
 ? ?/// 
 ? ?///        如果指定的參數(shù)有值,當(dāng)只有參數(shù)中指定的屬性發(fā)生過(guò)更改則返回真(True),否則返回假(False);
 ? ?///        如果指定的參數(shù)為空(null)或空數(shù)組,當(dāng)實(shí)體中任意屬性發(fā)生過(guò)更改則返回真(True),否則返回假(False)。
 ? ?///    
 ? ?bool HasChanges(params string[] names);

 ? ?/// 
 ? ?/// 獲取實(shí)體中發(fā)生過(guò)變更的屬性集。
 ? ?/// 
 ? ?/// 如果實(shí)體沒(méi)有屬性發(fā)生過(guò)變更,則返回空(null),否則返回被變更過(guò)的屬性鍵值對(duì)。
 ? ?IDictionary GetChanges();

 ? ?/// 
 ? ?/// 嘗試獲取指定名稱(chēng)的屬性變更后的值。
 ? ?/// 
 ? ?/// 指定要獲取的屬性名。
 ? ?/// 輸出參數(shù),指定屬性名對(duì)應(yīng)的變更后的值。
 ? ?/// 如果指定名稱(chēng)的屬性是存在的并且發(fā)生過(guò)變更,則返回真(True),否則返回假(False)。
 ? ?/// 注意:即使指定名稱(chēng)的屬性是存在的,但只要其值未被更改過(guò),也會(huì)返回假(False)。
 ? ?bool TryGetValue(string name, out object value);

 ? ?/// 
 ? ?/// 嘗試設(shè)置指定名稱(chēng)的屬性值。
 ? ?/// 
 ? ?/// 指定要設(shè)置的屬性名。
 ? ?/// 指定要設(shè)置的屬性值。
 ? ?/// 如果指定名稱(chēng)的屬性是存在的并且可寫(xiě)入,則返回真(True),否則返回假(False)。
 ? ?bool TrySetValue(string name, object value);
}
設(shè)計(jì)思想

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

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

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

有了算法,我們寫(xiě)一個(gè)簡(jiǎn)單范例來(lái)感受下:

public class Person : IEntity
{
 ? ?#region 靜態(tài)字段
 ? ?private static readonly string[] __NAMES__ = new string[] { "Name", "Gender", "Birthdate" };
 ? ?private static readonly Dictionary> __TOKENS__ = new Dictionary>()
 ? ?{
 ? ? ? ?{ "Name", new PropertyToken(0, target => target._name, (target, value) => target.Name = (string) value) },
 ? ? ? ?{ "Gender", new PropertyToken(1, target => target._gender, (target, value) => target.Gender = (Gender?) value) },
 ? ? ? ?{ "Birthdate", new PropertyToken(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 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 GetChanges()
 ? ?{
 ? ? ? ?if(_MASK_ == 0)
 ? ? ? ? ? ?return null;

 ? ? ? ?var dictionary = new Dictionary(__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
{
 ? ?public PropertyToken(int ordinal, Func getter, Action setter)
 ? ?{
 ? ? ? ?this.Ordinal = ordinal;
 ? ? ? ?this.Getter = getter;
 ? ? ? ?this.Setter = setter;
 ? ?}

 ? ?public readonly int Ordinal;
 ? ?public readonly Func Getter;
 ? ?public readonly Action Setter;
}

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

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

需要一個(gè)額外的整型數(shù)的實(shí)例字段 _MASK_ ,來(lái)標(biāo)記對(duì)應(yīng)更改屬性序號(hào);

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

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

性能對(duì)比

上面我們從代碼角度簡(jiǎn)單分析了下整個(gè)方案的性能和消耗,那么實(shí)際情況到底怎樣呢?跑個(gè)分唄(性能對(duì)比測(cè)試代碼地址:https://github.com/Zongsoft/Zongsoft.CoreLibrary/tree/feature-data/samples/Zongsoft.Samples.Entities),具體代碼就不在這里占用版面了,下面給出某次在我的老舊臺(tái)式機(jī)(CPU:Intel i5-3470@3.2GHz | RAM:8GB | Win10| .NET 4.6)上生成100萬(wàn)個(gè)實(shí)例的截圖:



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

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

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

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

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

上面的實(shí)現(xiàn)范例代碼并沒(méi)有實(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è)置器中的做了一個(gè)新舊值的比對(duì)判斷和對(duì) PropertyChanged 事件激發(fā),其他代碼沒(méi)有變化。

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

public class MyEntity : IEntity
{
    #region 標(biāo)記變量
    private readonly byte[] _MASK_;
    #endregion

    public 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)前屬性的順序號(hào)來(lái)確定到對(duì)應(yīng)的標(biāo)記數(shù)組的下標(biāo),然后再確定對(duì)應(yīng)的掩碼值。當(dāng)然,也別忘了調(diào)整 Zongsoft.Data.IEntity 接口中各方法的實(shí)現(xiàn)。

public class MyEntity : IEntity
{
 ? ?public bool HasChanges(params string[] names)
 ? ?{
 ? ? ? ?PropertyToken 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 GetChanges()
 ? ?{
 ? ? ? ?var dictionary = new Dictionary(__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)
    {
        /* 相對(duì)之前版本沒(méi)有變化 */
        /* No changes relative to previous versions */
    }
}

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

新問(wèn)題

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

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

新辦法

解決辦法其實(shí)很簡(jiǎn)單,正是本文的標(biāo)題——“動(dòng)態(tài)生成”,徹底解放實(shí)現(xiàn)者并確保實(shí)現(xiàn)的正確性。業(yè)務(wù)方不再定義具體的實(shí)體類(lèi),而是定義實(shí)體接口即可,實(shí)體類(lèi)將由實(shí)體生成器來(lái)動(dòng)態(tài)生成。我們依然“從場(chǎng)景出發(fā)”,先來(lái)看看業(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();
var employee = Entity.Build();
總結(jié)

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

提示

本文可能會(huì)更新,請(qǐng)閱讀原文: https://zongsoft.github.io/blog/zh-cn/zongsoft/entity-dynamic-generation-2,以避免因內(nèi)容陳舊而導(dǎo)致的謬誤,同時(shí)亦有更好的閱讀體驗(yàn)。

本作品采用?知識(shí)共享署名-非商業(yè)性使用-相同方式共享 4.0 國(guó)際許可協(xié)議 進(jìn)行許可。歡迎轉(zhuǎn)載、使用、重新發(fā)布,但必須保留本文的署名 鐘峰(包含鏈接:http://zongsoft.github.io),不得用于商業(yè)目的,基于本文修改后的作品務(wù)必以相同的許可發(fā)布。如有任何疑問(wèn)或授權(quán)方面的協(xié)商,請(qǐng)致信給我 ([email protected])。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/76655.html

相關(guān)文章

  • 實(shí)體類(lèi)的動(dòng)態(tài)生成(一)

    摘要:前言在應(yīng)用開(kāi)發(fā)中,通常都會(huì)涉及各種實(shí)體類(lèi)的編寫(xiě),有時(shí)這些實(shí)體類(lèi)還需要實(shí)現(xiàn)接口以支持屬性變更通知,一般我們都會(huì)手寫(xiě)這些代碼或者通過(guò)工具根據(jù)數(shù)據(jù)庫(kù)表定義抑或別的什么模板映射文件之類(lèi)的來(lái)生成它們。 前言 在應(yīng)用開(kāi)發(fā)中,通常都會(huì)涉及各種 POJO/POCO 實(shí)體類(lèi)(DO, DTO, BO, VO)的編寫(xiě),有時(shí)這些實(shí)體類(lèi)還需要實(shí)現(xiàn) INotifyPropertyChanged 接口以支持屬性變更...

    crossea 評(píng)論0 收藏0
  • Java動(dòng)態(tài)代理 jdk和cglib的實(shí)現(xiàn)比較

    摘要:與靜態(tài)代理對(duì)比,動(dòng)態(tài)代理是在動(dòng)態(tài)生成代理類(lèi),由代理類(lèi)完成對(duì)具體方法的封裝,實(shí)現(xiàn)的功能。本文將分析中兩種動(dòng)態(tài)代理的實(shí)現(xiàn)方式,和,比較它們的異同。那如何動(dòng)態(tài)編譯呢你可以使用,這是一個(gè)封裝了的庫(kù),幫助你方便地實(shí)現(xiàn)動(dòng)態(tài)編譯源代碼。 發(fā)現(xiàn)Java面試很喜歡問(wèn)Spring AOP怎么實(shí)現(xiàn)的之類(lèi)的問(wèn)題,所以寫(xiě)一篇文章來(lái)整理一下。關(guān)于AOP和代理模式的概念這里并不做贅述,而是直奔主題,即AOP的實(shí)現(xiàn)方...

    h9911 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

mochixuan

|高級(jí)講師

TA的文章

閱讀更多
最新活動(dòng)
閱讀需要支付1元查看
<