摘要:前言在應(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 接口以支持屬性變更通知,一般我們都會(huì)手寫(xiě)這些代碼或者通過(guò)工具根據(jù)數(shù)據(jù)庫(kù)表定義抑或別的什么模板、映射文件之類(lèi)的來(lái)生成它們。
但是,在業(yè)務(wù)實(shí)現(xiàn)中往往伴隨著諸如“如何簡(jiǎn)單且高效的獲取某個(gè)實(shí)體實(shí)例有哪些屬性發(fā)生過(guò)變更?”、“變更后的值是什么?”這樣的問(wèn)題,而大致的解決方法有:
由實(shí)體容器來(lái)跟蹤實(shí)例的屬性變更;
改造實(shí)體類(lèi)(譬如繼承特定實(shí)體基類(lèi),在基類(lèi)中實(shí)現(xiàn)這些基礎(chǔ)構(gòu)造)。
方法(1)需要配合一整套架構(gòu)設(shè)計(jì)來(lái)提供支撐,也不是專(zhuān)為解決上述實(shí)體類(lèi)的問(wèn)題而設(shè),并且實(shí)現(xiàn)和使用也都不夠簡(jiǎn)單高效,故此略過(guò)不表。接下來(lái)我將通過(guò)幾篇文章來(lái)詳細(xì)闡述這些問(wèn)題的來(lái)由以及解決方案,并給出完整的代碼實(shí)現(xiàn)以及性能比對(duì)測(cè)試。
關(guān)于源碼下面將要介紹的所有代碼均位于我們的開(kāi)源系列項(xiàng)目(地址:https://github.com/Zongsoft),項(xiàng)目主要采用 LGPL 2.1授權(quán)協(xié)議,歡迎大家參與并使用(請(qǐng)遵照授權(quán)協(xié)議)。而本文相關(guān)的源碼位于其中 Zongsoft.CoreLibrary 項(xiàng)目的 feature-data 分支(https://github.com/Zongsoft/Zongsoft.CoreLibrary/tree/feature-data)及其中的 /samples/Zongsoft.Samples.Entities 范例項(xiàng)目,由于目前我正在忙著造 Zongsoft.Data 數(shù)據(jù)引擎這個(gè)輪子,不排除后面介紹到的代碼會(huì)有一些調(diào)整,待該項(xiàng)目完成后這些代碼亦會(huì)合并到 master 分支中,敬請(qǐng)留意。
基礎(chǔ)版本萬(wàn)里長(zhǎng)城也是從第一塊磚頭開(kāi)始磊起來(lái)的,就讓我們來(lái)搬第一塊磚吧:
public class User { private uint _userId; private string _name; // 傳統(tǒng)寫(xiě)法 public uint UserId { get { return _userId; } set { _userId = value; } } // C# 7.0 語(yǔ)法 public string Name { get => _name; set => _name = value; } // 懶漢寫(xiě)法:僅限不需要操作成員字段的場(chǎng)景 public string Namespace { get; set; } }
以上代碼特地用了三種編碼方式,它們被C#編譯器生成的IL沒(méi)有模式上的不同,故而性能沒(méi)有任何區(qū)別,大家根據(jù)自己的口味采用某種即可,因?yàn)槲覀兊脑创a由于歷史原因可能會(huì)有一些混寫(xiě),在此一并做個(gè)展示而已。
由于業(yè)務(wù)需要,我們希望實(shí)體類(lèi)能支持屬性變更通知,即讓它支持 INotifyPropertyChanged 接口,這么簡(jiǎn)單的需求當(dāng)然不在話下:
public class User : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; private uint _userId; private string _name; public uint UserId { get => _userId; set { if(_userId == value) return; _userId = value; this.OnPropertyChanged("UserId"); // 傳統(tǒng)寫(xiě)法 } } public string Name { get => _name; set { if(_name == value) return; _name = value; this.OnPropertyChanged(nameof(Name)); // nameof 為 C# 7.0 新增操作符 } } protected virtual void OnPropertyChanged(string propertyName) { // 注意 ?. 為 C# 7.0 新增操作符 this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
一切看起來(lái)是那么完美,但是,當(dāng)我們寫(xiě)了幾個(gè)這樣的實(shí)體類(lèi),尤其是有些實(shí)體類(lèi)的屬性還不少時(shí),體驗(yàn)就有點(diǎn)糟糕了。自然我們會(huì)想到寫(xiě)個(gè)實(shí)體基類(lèi)來(lái)實(shí)現(xiàn)屬性變更通知的基礎(chǔ)構(gòu)造,當(dāng)然,在某些特定場(chǎng)景也可以通過(guò)工具來(lái)生成類(lèi)似上面這樣的C#實(shí)體類(lèi)文件,但工具生成的方式有一定局限性并且不易維護(hù)(譬如需要在生成的代碼基礎(chǔ)上進(jìn)行特定改造),在此不再贅述。
實(shí)體基類(lèi)在進(jìn)行基礎(chǔ)類(lèi)庫(kù)或API設(shè)計(jì)的時(shí)候,我有個(gè)建議:__從應(yīng)用場(chǎng)景開(kāi)始__。具體的作法是,先嘗試編寫(xiě)使用這些API的應(yīng)用代碼,待各種應(yīng)用場(chǎng)景的使用代碼基本都完成后,API接口也就自然而然的確定了。譬如,在我們這個(gè)需求中我希望這么去使用實(shí)體基類(lèi):
public class User : ModelBase { private uint _userId; private string _name; public uint UserId { get => _userId; set => this.SetPropertyValue(nameof(UserId), ref _userId, value); } public string Name { get => _name; set => this.SetPropertyValue(nameof(Name), ref _name, value); } }
有了這樣的實(shí)體基類(lèi)后,增強(qiáng)了功能后代碼依然如第一塊磚的“基礎(chǔ)版本”一樣簡(jiǎn)潔,真是高興啊!但這就夠了么,能不能把具體實(shí)體類(lèi)里面的成員字段也省了,交給基類(lèi)來(lái)處理呢?嗯,有點(diǎn)意思,試著寫(xiě)下應(yīng)用場(chǎng)景代碼:
public class User : ModelBase { public uint UserId { get => (uint)this.GetPropertyValue(nameof(UserId)); set => this.SetPropertyValue(nameof(UserId), value); } }
看起來(lái)棒極了,代碼變得更簡(jiǎn)潔了,真是天才?。〉?,喪心病狂的 C# 設(shè)計(jì)者似乎看到了這種普遍的需求,于是在 C# 5 中增加了 System.Runtime.CompilerServices.CallerMemberNameAttribute 自定義標(biāo)記,C# 編譯器將自動(dòng)把調(diào)用者名字生成出來(lái)傳遞給加注了該標(biāo)記的參數(shù),因此這樣的代碼還可以繼續(xù)簡(jiǎn)化:
public class User : ModelBase { public uint UserId { get => (uint)this.GetPropertyValue(); set => this.SetPropertyValue(value); } }
但是,屬性的 getter 里面的那個(gè)類(lèi)型強(qiáng)制轉(zhuǎn)換,怎么看都像是一朵“烏云”啊,能不能把它也去掉呢?嗯,利用C#的泛型類(lèi)型推斷可以完美解決它,繼續(xù)強(qiáng)勢(shì)進(jìn)化:
public class User : ModelBase { public uint UserId { get => this.GetPropertyValue(() => this.UserId); set => this.SetPropertyValue(() => this.UserId, value); } }
哇喔,有點(diǎn)小崇拜自己了,這代碼漂亮的一批!至此,實(shí)體基類(lèi)的API接口基本確定,已經(jīng)迫不及待想要去實(shí)現(xiàn)它了。
提示:由于采用 CallerMemberNameAttribute 自定義標(biāo)記的參數(shù)會(huì)導(dǎo)致 C# 編譯器要求該參數(shù)必需有默認(rèn)值,因此有些 SetPropertyValue(...) 方法重載版本中 propertyName 參數(shù)需要位于參數(shù)集的最后,為了與上面的范例代碼對(duì)應(yīng)就省略了這些參數(shù)的標(biāo)記,并保持與原有范例相同的簽名設(shè)計(jì)。
using System; using System.Linq.Expressions; public class ModelBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected object GetPropertyValue([CallerMemberName]string propertyName = null); protected T GetPropertyValue(Expression > property); protected void SetPropertyValue (string propertyName, ref T field, T value); protected void SetPropertyValue (string propertyName, T value); protected void SetPropertyValue (Expression > property, T value); }
實(shí)體基類(lèi)的實(shí)現(xiàn)主要思路就是采用字典來(lái)記錄各屬性的變更值,有了這個(gè)基礎(chǔ),要繼續(xù)增加諸如“獲取哪些屬性發(fā)生過(guò)變更”之類(lèi)的需求自然就很容易了:
public class ModelBase : INotifyPropertyChanged { // other members public bool HasChanges(params string[] propertyNames); public IDictionaryGetChangedPropertys(); }
具體的代碼就不在這里貼出了,有興趣的可以參考:https://github.com/Zongsoft/Zongsoft.CoreLibrary/blob/master/src/Common/ModelBase.cs,從功能角度上看,目前的設(shè)計(jì)還是不錯(cuò)的。但是,某些方法的設(shè)計(jì)有嚴(yán)重性能缺陷的,主要有以下幾點(diǎn):
每次讀寫(xiě)屬性都會(huì)解析Lambda 表達(dá)式的操作會(huì)產(chǎn)生巨大的性能損耗;
采用字典來(lái)保存實(shí)體屬性值的設(shè)計(jì)機(jī)制,會(huì)導(dǎo)致值類(lèi)型的屬性讀寫(xiě)反復(fù)被裝箱(Boxing)、拆箱(Unboxing);
字典的讀寫(xiě)效率也遠(yuǎn)低于直接操作成員字段的語(yǔ)言原語(yǔ)方式。
綜上所述,雖然目前方案有性能缺陷,但應(yīng)對(duì)一般場(chǎng)景其實(shí)是沒(méi)有問(wèn)題的,而且功能和易用性方面都是很好的;但是,性能對(duì)于后臺(tái)程序猿而言猶如懸在頭頂?shù)?達(dá)摩克利斯之劍,這正是這個(gè)系列文章要最終解決的問(wèn)題。在此之前,如果大家有關(guān)于這個(gè)問(wèn)題的性能優(yōu)化方案,歡迎關(guān)注我們的公眾號(hào)(Zongsoft)留言討論。
敬請(qǐng)期待更精彩的下篇,關(guān)注我們的公眾號(hào)可以第一時(shí)間看到哦!
本文可能會(huì)更新,請(qǐng)閱讀原文: https://zongsoft.github.io/blog/zh-cn/zongsoft/entity-dynamic-generation-1,以避免因內(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/76656.html
摘要:源碼位于項(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ě)字段的方式而言依然有巨...
摘要:與靜態(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)方...
閱讀 3268·2021-11-24 09:39
閱讀 3218·2021-10-21 09:38
閱讀 2424·2019-08-29 15:28
閱讀 3768·2019-08-26 12:23
閱讀 2646·2019-08-26 12:19
閱讀 1381·2019-08-23 12:44
閱讀 2154·2019-08-23 12:02
閱讀 1079·2019-08-22 17:05