主頁 > .NET開發 > [C# 中的序列化與反序列化](.NET 原始碼學習)

[C# 中的序列化與反序列化](.NET 原始碼學習)

2022-11-21 06:18:57 .NET開發

[C# 中的序列化與反序列化](.NET 原始碼學習)

關鍵詞:序列化(概念與分析)    三種序列化(底層原理 原始碼)    Stream(底層原理 原始碼)    反射(底層原理 原始碼)

假如有一天我們要在在淘寶上買桌子,桌子這種很不規則不東西,該怎么從一個城市運輸到另一個城市,這時候一般都會把它拆掉成板子,再裝到箱子里面,就可以快遞寄出去了,這個程序就類似我們的序列化的程序(把資料轉化為可以存盤或者傳輸的形式),當買家收到貨后,就需要自己把這些板子組裝成桌子的樣子,這個程序就像反序列的程序(轉化成當初的資料物件),

序列化是指將物件轉換成位元組流,從而存盤物件或將物件傳輸到記憶體、資料庫或檔案的程序, 它的主要用途是保存物件的狀態,以便能夠在需要時重新創建物件,反向程序稱為“反序列化”,有點類似于壓縮與解壓的程序,

【# 請先閱讀注意事項】

【注:

(1)   文章篇幅較長,可直接轉跳至想閱讀的部分,

(2)   以下提到的復雜度僅為演算法本身,不計入演算法之外的部分(如,待排序陣列的空間占用)且時間復雜度為平均時間復雜度,

(3)   除特殊標識外,測驗環境與代碼均為 .NET 6/C# 10,

(4)   默認情況下,所有解釋與用例的目標資料均為升序,

(5)   默認情況下,圖片與文字的關系:圖片下方,是該幅圖片的解釋,

(6)   文末“ [ # … ] ”的部分僅作補充說明,非主題(演算法)內容,該部分屬于 .NET 底層運行邏輯,有興趣可自行參閱,

(7)   本文內容基本為本人理解所得,可能存在較多錯誤,歡迎指出并提出意見,謝謝,】

 

【注:

1.     本文在此僅介紹序列化的使用方法及相關表層內容,礙于篇幅,原始碼分析將在之后的文章中進一步介紹】

2.     本文每一個分析程序間的聯系性可能較低,建議先閱讀總結部分,再閱讀正文

3.     此篇文章內容較為復雜,篇幅較大建議分段閱讀、先看總結再看內容】

一、序列化的作用與意義

先考慮壓縮與解壓,我們與一堆保存了資訊的檔案,現在需要將其通過網路發送給其他人,相信我們不會直接一個一個檔案的傳,而是將其放在一個檔案夾或作為一個壓縮包后在傳遞,這樣,即節省了空間,又加快了傳輸,同時將其打包后也讓我們在之后對這一堆檔案有更好的管理,

  1. 傳輸,舉個例子,一座大廈好比一個物件,現在計劃要把這座大廈搬到另一個地方去,直接挪肯定不太現實,(一般地,網路傳輸只能通過位元組流,不能直接傳輸物件),因此我們就把大廈拆成每一塊磚,給每塊磚定一個編號,知道這是在大廈的哪一部分,在這個程序中序列化就起到了將大廈分成磚頭的作用,方便資料的互動
  2. 存盤,在某些程式運行時會產生一些物件,這些物件隨著程式的停止而消失,但如果我們想把某些物件保存下來,在程式終止運行后,繼續讓這些物件存在,可以使程式再次運行時讀取這些物件的值,或在其他程式中利用這些保存下來的物件,我們將這個程序命名為序列化,最常見的:Ctrl C / X,Ctrl V,

這時候就又有一個問題:為什么要將其序列化后再讀寫而不直接對物件本身進行讀寫?

我們要將物件寫入一個磁盤檔案,再將其讀出來,會產生什么問題?其中一個最大的問題就是物件參考,再舉個例子,假設現在有兩個類,A 與 B,B類中含有一個指向A類物件的參考,現在我們對兩個類進行實體化 { A a = new A(); B b = new B(); },這時在記憶體中實際上分配了兩個空間,一個存盤物件a,一個存盤物件b,接下來我們將它們寫入到磁盤的一個檔案中去,就在寫入檔案時出現了問題,因為物件b包含對于物件a的參考,所以系統會自動的將a的資料復制一份到b,這樣的話當我們從檔案中恢復物件時(也就是重新加載到記憶體中)時,記憶體分配了三個空間,而物件a同時在記憶體中存在兩份【注意:此處的復制指的是檔案的復制,并非程式運行時的淺層復制,因此對于 a 會產生新的兩個無關物件】,此時,若想在檔案上修改物件a的資料的話,就要搜索它的每一份拷貝來達到物件資料的一致性這樣增加了不少負擔,而序列化就解決了這樣的問題,

序列化的機制:

(1)保存到磁盤的所有物件都獲得一個序列號(1, 2, 3…)

(2)當要保存一個物件時,先檢查該物件是否被保存了,

(3)如果以前保存過,只需寫入與已經保存的具有序列號 k 的物件相同的標記;否則,保存該物件

利用編號的方法,解決了物件參考的問題,類似于程式設計中的復用,

小結,需要序列化的原因:

  1. 因為在網路傳輸時,一般只能使用資料流的形式,需要將物件轉換為便于傳輸形式,
  2. 某些情況下需要保存一些物件的特定情況,供其他時候使用,

二、基本序列化方式及其效率

使用 BinaryFormatter 進行串行化的二進制形式序列化(必須添加 System.Runtime.Serialization.Formatters.Binary; 命名空間);

使用SOAP協議進行的序列化;

使用 XmlSerializer 進行串行化的XML形式序列化物件;

JSON 序列化,

【注:如果一個類所創建的物件,能夠被序列化,那么要求必須給這個類加上 [Serializable] 特性】

(一) 二進制序列化

需要引入命名空間

—— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— ——

定義一個類,用于作為序列化的物件

—— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— ——

定義待處理物件

—— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— ——

定義一下序列化與反序列化方法

【思考:為什么不能用 Line 46 行的陳述句?】

因為在類中,我們采用的是簡便屬性,且采用構造方法對欄位直接賦值,而簡便屬性似乎無法回傳直接通過欄位賦值的欄位值(此推論和本人之前的映像不太相符,歡迎各位學者提出觀點)因此該物件的此屬性值恒為 null,

如果將屬性補全,則可以避免這樣的問題:

—— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— ——

然而,運行的時候發現了問題:

  • 由此得出一個結論:需要用 [Serializable] 特性修飾對應的類,否則無法將該類的物件序列化但個人認為,應該是在不需的地方加上NonSerialized才更合理,

—— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— ——

我們為這個類加上相應標簽再來跑一次

—— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— ——

序列化后檔案中的內容:

在程式所在的相關的檔案夾內生成了一個 .bin 型別的檔案,說實話我有點看不懂它為什么要存盤成這樣的形式(不排除我的編碼型別導致的問題),理論上應該是以二進制的方式呈現資料,

—— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— ——

反序列化后的結果:

—— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— ——

從剛才得出的結論再入手,那我們可不可以指定某些元素不讓其序列化呢?答案是可以的

只需要在相應元素前加上這個特性即可,

看看效果:

可以發現,因為沒有序列化欄位 age,因此檔案中也沒有了 age 的身影;反序列后輸出了 int 型別的默認初始值,

—— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— ——

—— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— ——

既然有三種序列化的方式,那當然要比較一下其性能,

為了較好的得出能效差異,此處采用4個物件進行序列化與反序列化操作,每個物件包含 1e7(實測為該狀態下本人電腦的極限值) 個其他物件,這些物件中每個包含兩個欄位,如下圖:

—— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— ——

結果:(運行時間與生成檔案的大小)

 

由于每次進行一個周期均會覆寫原序列化的文本,因此此處的檔案大小,僅代表一個周期(一次序列化 + 一次反序列化)生成的檔案大小,即 1e7 的物件數量,

(二) XML 序列化

首先簡單介紹一下 XML 格式,

可擴展標記語言( eXtensible Markup Language,標準通用標記語言的子集)是一種簡單的資料存盤語言,使用一系列簡單的標記描述資料,而這些標記可以用方便的方式建立,雖然可擴展標記語言占用的空間比二進制資料要占用更多的空間,但可擴展標記語言極其簡單易于掌握和使用

總結一下特點:利用更簡單的一些標記去描述資料,使得資料使用更加方便,用空間換取便捷,

—— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— ——

需要引入命名空間

—— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— ——

還是用那個類,定義一下序列化與反序列化方法

 

可以發現,二者在格式上其實差別不大,程序均是確定檔案、序列化或反序列化、寫入或讀取,

—— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— ——

簡單看一下效果

但除錯程序中發生了錯誤:

注意看此處的報錯,“Only public types can be processed” 也就是說,只有公共型別,才能被 xml 序列化,因此,需要將類 Person 標記為 public,

不過對于 XML 序列化,并不需要將序列化物件標記為 [Serialize],

—— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— ——

結果如下:

 

—— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— ——

不知道各位有沒有注意到一個問題

二進制序列化:

Xml 序列化:

對比可以發現,二進制序列化時訪問的是物件的欄位;Xml 序列化時訪問的是物件的屬性,所以當使用簡便屬性,且通過構造方法直接對欄位賦值時,因為無法通過屬性獲取到欄位的值,因此在進行 Xml 序列化時會出現例外:

—— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— ——

同樣,來測驗一下性能:

 

同理,由于每次進行一個周期均會覆寫原序列化的文本,因此此處的檔案大小,僅代表一個周期(一次序列化 + 一次反序列化)生成的檔案大小,即 1e7 的物件數量,

可以看到,相較于二進制序列化,Xml在時間上明顯減少,但消耗了接近兩倍的空間,頗有一種空間換時間的感覺,

—— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— ——

(三) 基于 SOAP 協議的序列化

SOAP 和在操作上二進制流序列化差別不大;結果上和 Xml 差別不大,只是 SOAP 不能序列化泛型物件,因此在序列化時要將待序列化的物件轉換成陣列形式,,

先來介紹一下 SOAP 協議:SOAP 是基于 XML 的簡易協議,可使應用程式在 HTTP 之上進行資訊交換,更簡單地說:SOAP 是用于訪問網路服務的協議,

—— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— ——

【注:由于無法載入命名空間 System.Runtime.Serialization.Formatters.Soap ;微軟檔案也沒有查找到相關資訊,因此在此不作演示】

(四) JSON 序列化

JSON(JavaScript Object Notation, JS物件簡譜)是一種輕量級的資料交換格式,它基于 ECMAScript(European Computer Manufacturers Association, 歐洲計算機協會制定的js規范)的一個子集,采用完全獨立于編程語言的文本格式來存盤和表示資料,簡潔和清晰的層次結構使得 JSON 成為理想的資料交換語言, 易于人閱讀和撰寫,同時也易于機器決議和生成,并有效地提升網路傳輸效率,【百度百科 JSON_百度百科 (baidu.com)】

—— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— ——

需要引入命名空間

據微軟的說法:

 

后續在學習原始碼時,會進一步分析二者異同,

—— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— ——

定義序列化與反序列化方法

可以發現,其無需初始化用于序列化的物件,推測應該是方法在該類中被定義為 static,這樣的方式使得使用更加便捷,也在一定程度上節省了空間,

—— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— ——

結果展示

其和 Xml 也是一樣,讀取物件的屬性而不讀取欄位,且存盤本質為字串,非常簡潔,這也為其高效傳輸與廣泛應用奠定了基礎,

—— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— ——

性能測驗:

可以看到,單從表象,JSON 序列化幾乎整合了二進制序列化和 XML 序列化的優點:不僅生成的檔案體積小、周期運行速度也快,

總結

1. 序列化是一種處理資料的方式,將代碼中的物件或元素轉化為某種具有意義和規律的流形式(文本流,字串流等),便于進行存盤、分析與傳輸,

2. 序列化主要用在資料持久化和遠程呼叫,把物件狀態保存到流中,達到持久化(或遠程呼叫)的作用,比如有一個類有100個屬性欄位,如果在其他地方使用這個被實體化的類就必須讀取100次它的屬性以獲取物件的狀態資訊,才能利用這些資訊構建新類,而有了序列化就可以將類資訊保存到一個流中,要構造新類時候直接反序列化,將所有屬性直接付給新實體,這比手工寫代碼讀取屬性方便,還實作了持久化,

3. 三種序列化的對比:

(1)二進制流序列化:

性能測驗結果:時間 101582.1859 ms,空間 228 MB * 4

需要對序列化物件進行特性 [Serialize] 標記,

  • 優點:對資料的保真度很高,對于多次呼叫應用程式時保持物件狀態非常有用,例如,通過將物件序列化到剪貼板,可在不同的應用程式之間共享物件;將物件序列化到流、磁盤、記憶體和網路等;遠程處理使用序列化;“按值”在計算機或應用程式域之間傳遞物件,
  • 缺點:

    a) 如果使用不同的 .NET 版本序列化和反序列化以 UTF-8 或 UTF-7 編碼的物件,則不保留該物件的狀態,即,在不同框架與編碼型別下,可能會產生沖突例外或不保存物件,

    b)序列化/反序列化所用時間較長,且序列化內容不易被直接看懂,

(2)XML 序列化:

性能測驗結果:時間 43889.8765 ms,空間 476 MB * 4

需要將物件進行標記為 public,

  • 優點:

    a) 相較于二進制流序列化,在時間效率上有所提升,

    b) 序列化結果具有一定可讀性,

    c) 基于其衍生出的 SOAP 協議序列化方式,具有安全性、可擴展性、跨語言、跨平臺以及支持多種傳輸形式等優點,

    d) 只序列化公共屬性和欄位,當希望提供或使用資料而不限制使用該資料的應用程式時,這一點非常有用,由于 XML 是開放式的標準,因此它對于通過 Web 共享資料來說是一個理想選擇;SOAP 同樣是開放式的標準,這使它也成為一個理想選擇,

  • 缺點:由于采用大量標記去標識每個物件,使得序列化結果冗長復雜,對空間的額外開銷增大,

(3)JSON 序列化:

性能測驗結果:時間 24381.7978 ms,空間 267 MB * 4,

  • 優點:

    a) 整合了二進制序列化占用空間小與 XML 序列化速度快的優點,

    b) 序列化結果具有極佳的可讀性與簡潔性,

    c) 相對于 XML 協議決議速度更快,

    d) 只序列化公共屬性,且JSON 是開放式的標準,對于通過 Web 共享資料來說是一個理想選擇,

  • 缺點:

    a) 沒有統一可用的 IDL(Interface description language 介面描述語言)即,跨平臺介面,延長了開發周期,

    b) 在某些語言中需要采用反射機制,不適用于 ms 級回應,

三、三種序列化方式的實作原理

【注:由于關于該部分原始碼分析的內容與資料較少,且本人水平有限,不能闡述得很清晰或完全正確,還請各位讀者指正與提出意見,謝謝】

(一) 二進制序列化 BinaryFormatter

1. 基本資訊

位于程式集 System.Runtime.Serialization.Formatters.dll,命名空間 System.Runtime.Serialization.Formatters.Binary 中,

—— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— ——

密封類,繼承了介面 IFormatter,該介面包含兩個方法 Serialize() 與 Deserialize(),主要用于提供格式化串行化物件的功能,在不同情況下根據需要覆寫介面中的方法,以達到多型的目的,

該介面專門用于定義具體的序列化和反序列化方式

  • Line 14:回傳值型別為 object,引數為 Stream 型別的反序列化方法,【有關 Stream 會在文末進行補充說明】
  • Line 19:無回傳值,引數為 Stream 型別與 object 型別的序列化方法,
  • Line 13、17:注意到這兩個方法均被標記為 Obsolete(過時的),也就是說出于某種原因,這種方法已被廢棄,存在某些更新的方法代替,
  • Line 24:型別為 ISurrogateSelector 屬性 SurrogateSelector,其中,介面 ISurrogateSelector 的作用是幫助格式化程式選擇代理以委托給其他物件的序列化或反序列化

解釋一下,為了使序列化/反序列化機制作業起來,需要定義一個”代理型別”,它接受對現有型別進行序列化和反序列化的操作,在正式執行前,先向格式化器記錄該代理型別的一個實體,告訴格式化器,代理型別要作用于現有的哪一個型別,格式化器檢測到它正要對現在型別的一個實體進行序列化和反序列化時,會呼叫由該代理物件定義的方法,

注:具體運行流程將在后文分析

  • Line 29:類 SerializationBinder,允許用戶控制類的加載并指定要加載的類,用于控制在序列化和反序列化期間使用的實際型別,

在序列化程序中,格式化程式傳輸需要創建正確型別和對應版本的物件實體的資訊,通常包括物件的完整型別名稱和程式集名稱,默認情況下,反序列化可使用此資訊創建相同物件的實體,由于原始類可能在執行反序列化的計算機上不存在,如:原始類已在程式集之間移動,或者服務器和客戶端要求使用不同的類版本,因此有些用戶可能需要控制要序列化和反序列化哪個類,

在創建和記錄資訊時有兩種方式:

(1)BindtoName(),記錄物件的型別(Type),回傳物件所在的的程式集名(assemblyName)與所屬的型別名稱(typeName),

(2)BindToType(),記錄物件所在的的程式集名(assemblyName)與所屬的型別名稱(typeName),回傳物件的型別(Type),

  • Line 34:結構體 StreamingContext,用于說明給定序列化流的源和目標,并提供另一個呼叫方定義的背景關系,簡單來說就是添加一些資訊,是的資料的來源去向清晰化,

—— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— ——

1 個只讀變數和7 個欄位

  • Line 210:類 ConcurrentDictionary<TKey,TValue> 表示可由多個執行緒同時訪問的鍵/值對的執行緒安全集合,其中,所有公共成員和受保護成員 ConcurrentDictionary<TKey,TValue> 都是執行緒安全的,并且可以從多個執行緒并發使用,但是,通過實作(包括擴展方法) ConcurrentDictionary<TKey,TValue> 之一訪問的成員不能保證執行緒安全,并且可能需要由呼叫方同步,
  • Line 213:介面 ISurrogateSelector 指示序列化代理項選擇器類,代理項選擇器實作 ISurrogateSelector 介面,以幫助格式化程式選擇代理以委托給其他物件的序列化或反序列化,有關代理器更多內容,之后會提到,
  • Line 216:結構體 StreamingContext 說明給定序列化流的源和目標,并提供另一個呼叫方定義的背景關系,主要用于資訊的存盤,包括但不限于序列化前后的物件內容,
  • Line 219:類 SerializationBinder 允許用戶控制類加載并指定要加載的類,主要配合代理選擇器使用,加上版本容錯機制,可以在一定程度上實作不同版本間的序列化與反序列化操作,
  • Line 222、225、228:此處的三個列舉在后文均有提及,在此不做介紹,
  • Line 213:一個型別為 object 的陣列,用于存盤序列化后的結果,作為一份“備份”記錄結果,供反序列化時使用,

2. 序列化流程

  • Line 179:引數 serializationStream 表示待序列化的資料流型別(主要包括:檔案流 FileStream、記憶體流 MemoryStream、網路流 NetworkStream、加密流 CryptoStream、文本讀寫 StreaReader 與 StreamWriter、二進制讀寫 BinaryReader 與 BinaryWirter);graph 表示待序列化的物件,
  • Line 181:用于判斷當前狀態下能否進行二進制序列化,據微軟的說法,由于存在安全漏洞,該方法現已過時,并生成 ID 為 SYSLIB0011 的編譯時警告,此外,在 .NET 7 及 ASP.NET Core 5.0 及更高版本的應用中,除非 Web 應用已重新啟用 BinaryFormatter 功能,否則它們會引發 NotSupportedException 的例外(詳細內容請參閱 中斷性變更:BinaryFormatter 序列化方法已過時,并且已在 ASP.NET 應用中禁用 - .NET | Microsoft Learn),
  • Line 185:如果待序列化物件為空,則不能進行序列化操作,
  • Line 189:定義格式化列舉并賦值,為后續的序列化做準備,

其中,類 InternalFE,內部存盤了 4 類列舉

(1)FormatterTypeStyle 表示在序列化流中的布局格式

其中,TypesWhenNeeded 表示格式只能為物件陣列、Object型別ISerialized 非基元值型別所宣告的型別;TypesAlways 表示格式可以為所有物件成員和 ISerializable 物件成員;XsdString 表示可以采用 XSD(XML Schema Definition)格式(而不是 SOAP 格式)來提供字串

(2)FormatterAssemblyStyle 用于定位和加載程式集的方法,一定程度上規定了兼容性的問題,

Simple 表示在簡單模式下,反序列化期間所用的程式集不需要與序列化期間所用的程式集完全匹配,具體而言,當 LoadWithPartialName 方法加載程式集時,版本號不需要匹配

Full 表示在完全模式下,反序列化期間所用的程式集必須與序列化期間所用的程式集完全匹配;使用 Assembly 類的 Load 方法加載程式集,

(3)TypeFilterLevel 指定用于 .NET Framework 遠程處理的自動反序列化的級別,一定程度上規定了能進行處理的資料型別,

Low = 2,表示 .NET Framework 遠程處理的 Low (低)反序列化級別,支持與基本遠程處理功能相關聯的型別,

Full,表示 .NET Framework 遠程處理的 Full (完全)反序列化級別,它支持遠程處理在所有情況下支持的所有型別,

(4)InternalSerializerTypeE指定需要進行的序列化型別,

  • Line 197:開始進行序列化,并記錄日志,
  • Line 198:定義一個物件寫入器,并傳入引數包括代理型別、背景關系資訊、格式化器列舉、序列化/反序列化所控制的實際型別,
  • Line 199:定義二進制寫入器,并傳入引數包括待序列化的資料流型別、物件寫入器、序列化流中的布局格式,
  • Line 200:呼叫物件寫入器中的序列化方法,【這一步才是真正的開始序列化
  • Line 205:序列化結束,并記錄日志,

—— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— ——

下面分析一下 Line 200 處的詳細程序:

  • Line 32:開始寫入,
  • Line 38:獲取一個特殊的 ID 編號,【注:該方法內部涉及很多其他方法的呼叫,在此不一一分析,僅對程序做出說明

首先對方法 InternalGetId() 傳入引數:待序列化物件、是否將唯一 ID 分配給值型別、物件型別的資訊、是否新物件(此處的“新”值得是該物件在之前是否進行過序列化操作),

 Line 556:若該物件是之前(已經進行過序列化)的物件,則直接回傳其先前序列化后被分配的 ID,

Line 562:若該物件在之前沒有進行過序列化操作,且描述物件資訊不為空、沒有被分配過唯一的 ID,則為該待序列化物件計算一個唯一的 ID,

Line 571:若該物件在之前沒有進行過序列化操作,但出于某種原因無法計算新的 ID,則呼叫一個上層類(ObjectIDGenerator)中的公共方法,以獲得 ID,

Line 59:方法 FindElement(),元素定位,利用元素的哈希值在陣列 _objs 中查找待序列化物件 obj,并回傳其所在位置以及是否存在的標志(flag),

Line 61 ~ 78:若未找到相應物件,則將其記錄至陣列中,并計算相應 ID;否則直接回傳其對應的 ID,(此處的 ID 是根據物件的哈希值得出,類似于“記憶化搜索”,記錄已經處理過的物件,以便后續直接使用),

—— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— ——

  • Line 40:方法 WriteSerializedStreamHeader(),初始化序列化寫入流的起始器,
  • Line 41:將待序列化物件加入到準備佇列中,
  • Line 44:方法 GetNext(),依次從準備佇列中取出元素與其對應的 ID,直到佇列為空,
  • Line 47~55:將佇列中的元素轉換為 WriteObjectInfo 型別,該型別資料流 Stream 型別中的一種,主要用于流的寫入,
  • Line 57:型別 NameInfo,記錄物件的詳細資訊,包括以下內容:

  • Line 58:正式開始進行寫入,

Line 78:objectInfo 待寫入的物件;memberNameInfo 與 typeNameInfo 傳入的為同一個內容,存盤了物件的詳細資訊,

Line 87:Converter.s_typeofString,相當于字串型別,若待序列化物件為字串型別,則以字串的形式進行寫入,

Line 93:若待序列化物件為陣列型別,則以陣列的形式進行寫入,

【礙于篇幅,在此對于方法 WriteObjectString() 與 WriteArray() 就不放出原始碼,僅做簡單說明】

對于方法 WriteObjectString(),首先處理 Null 的部分,該程序根據物件中的 Null 數量,將所有 Null 進行處理,確保在之后的寫入中遇到 Null 時不會觸發例外 NullReference,Null 處理完后再對剩余部分進行序列化,整個序列化程序由方法 WriteByte()、WriteInt32() 與 WriteString() 完成,其作用是將一個位元組/整數/字串寫入檔案流中的當前位置,

對于方法 WriteArray(),通過遍歷的方式,說簡單些就是依次將陣列中的每個元素轉換后寫入檔案流,

Line 101:若待序列化元素既不是字串型別,也不是陣列型別,則獲取物件在快取 cache 中的名稱、型別以及資料本身,分別存盤到陣列 array、array2 與 array3 中,在初始化時已經將物件內部的個元素資訊分別存盤到了類的欄位中,在此處進行賦值,其按照訪問每個元素的方式,將每個元素的資訊存盤到陣列中,這樣做的原因可能是同一個物件中可能存在不同型別的元素,需要以不同方式進行序列化

 Line 102:若物件可以進行序列化操作,則標記并記錄資訊供后續使用,

Line 112:獲取型別,

Line 113:將該型別 type 轉換為某種編碼,判斷其是否為基元型別 && 判斷其是否不為字串型別,

Line 115~124:若元素不為空,則將元素操作后存盤與陣列 array4 中;否則根據元素型別,將操作后的資訊存盤于陣列 array4 中,

至此,初步轉換已經完成,之后再根據 array4 中的資訊,將物件的每個元素寫入 BinaryObjectWithMap 型別的遍歷中,并添加到 _objectMapTable,最終再根據 FileStream 寫入檔案,

小結一

1. 總結一下二進制序列化的流程:將待序列化物件分解為最小單元并獲取其型別,依次遍歷最小單元并在陣列中存盤其相關資訊,將其寫入資料流中,并復制一份結果存盤在陣列中,

2. 二進制序列化程序比較復雜,其需要針對每一位不同的元素型別以及出現的位置,將其轉換為能夠保存這些資訊的二進制碼,因此存在許多遍歷于轉換,效率較低,同時這也導致了反序列化的效率較低,雖然計算機對二進制數處理有著天然的優勢,但是在進行轉換與逆轉換的時候效率確實不高,

3. 根據自然規律,越少的表示單元就需要越多的組合來表示一個資訊,二進制碼只有 0 與 1 兩種單元,其需要儲存元素型別、位置、狀態及其他內容,使得一個元素需要轉換出很長的一串二進制碼,使得空間占用過多,

4. 二進制反序列化的時候會自動兼容處理序列化一方新增的資料,但是在個別情況下會出現反序列化的程序中遇到例外的情況,目前發現的出現反序列化例外的資料型別包括,泛型集合與陣列,這兩種資料結構并非是一定會導致二進制反序列化報錯,而是有一定的條件,泛型集合出現反序列化例外的條件有三個:

(1)序列化的物件新增了泛型集合;

(2)泛型使用的是新增的類;

(3)新增的類在反序列化的時候不存在;

陣列也是類似的,只有滿足上述三個條件的時候,才會導致二進制反序列化失敗,

具體原因可能與其版本容錯機制(Version Tolerant Serialization,VTS)有關,詳細內容請參閱(Version tolerant serialization | Microsoft Learn)

5. 據微軟官方的說法:

究其原因是:其會不安全地處理請求有效負載的威脅類別,可導致目標應用內出現拒絕服務 (DoS)、資訊泄露或遠程代碼執行,其中的 Deserialize() 方法可用作攻擊者對使用中的應用執行 DoS 攻擊的載體,這些攻擊可能導致應用無回應或行程意外終止,且使用 SerializationBinder 或任何其他 BinaryFormatter 配置開關都無法緩解此類攻擊,.NET 認為此行為是設計使然,因此不會發布代碼更新來修改此行為,所以微軟不建議使用二進制序列化,(感興趣的讀者可以深入研究,在此不作更多解釋)

當然,二進制序列化還是有一些優點:

6. 資料保密性強,這一點和可閱讀性是相反的,可閱讀性低則保密性強,

7. 序列化后的檔案,由于時二進制形式,因此便于計算機直接分析與操作,

(二) XML 序列化

1.  基本資訊

 

位于程式集 System.Private.Xml.dll,命名空間 System.Xml.Serialization 中,

—— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— ——

Xml 沒有繼承任何類以及介面,通過自定義序列化與反序列化方法,與很多多載方法,實作一種新的序列化形式,

—— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— ——

共 11 個欄位

  • Line 930:類 TempAssembly,與類 Assembly 關聯,基于反射可以獲得正在運行的裝配件資訊,也可以動態的加載裝配件,以及在裝配件中查找型別資訊,并創建該型別的實體,可以使用反射動態地創建型別的實體,將型別系結到現有物件,或從現有物件中獲取型別,然后呼叫其方法或訪問器欄位和屬性,

注:有關反射 Reflection 會在文末補充說明

  • Line 933:欄位 _typedSerializer,表示物件之前是否已經進行過 XML 序列化操作,
  • Line 936:抽象類Type,用來包含型別的特性,使用這個類的物件能讓我們獲取程式使用的型別的資訊,

補充一些關于這個類的資訊:

(1)對于程式中用到的每一個型別,CLR都會創建一個包含這個型別資訊的Type型別的物件,

(2)程式中用到的每一個型別都會關聯到獨立的Type型別的物件,

(3)不管創建的型別有多少個實體,只有一個Type物件會關聯到所有這些實體,

  • Line 939:抽象類 XmlMapping,支持 .NET 型別和 XML 架構資料型別之間的映射,相當于是一種規則,用于序列化與反序列化的正常進行,
  • Line 942:結構體 XmlDeserializationEvents,包含可用于將事件委托傳遞給 Deserialize 的執行緒安全的 XmlSerializer 方法的欄位,
  • Line 945:欄位 DefaultNamespace,獲取默認命名空間的命名空間 URI(Uniform Resource Identifier 標識、定位任何資源的字串),如果沒有默認命名空間,則為空字串,
  • Line 948:與 Line 936 處的欄位為同一型別,推測 _primitiveType 表示物件的基元型別(16種),_rootType 表示物件派生于的型別(System.ValueType、System.Enum、System.Object),
  • Line 951:欄位 _isReflectionBasedSerializer 表示物件是否是基于反射而實作序列化,
  • Line 954:類 TempAssemblyCache,存盤物件在快取內的資訊,包括但不限于:資料型別、反射資訊,
  • Line 957:類 XmlSerializerNamespaces,包含 XmlSerializer 用于在 XML 檔案實體中生成限定名的 XML 命名空間和前綴,
  • Line 960:定義字典,以型別為 Key,記錄映射關系(XmlMapping)與 序列化器,

2.  序列化流程

  • Line 278:引數

(1)xmlWriter 一個寫入器,提供一種快速、非快取和只寫入方式以生成包含 XML 資料的流或檔案;

(2)o 表示待序列化物件;

(3)namespace 包含 XmlSerializer 用于在 XML 檔案實體中生成限定名的 XML 命名空間和前綴;

(4)encodingStyle 物件的編碼型別,包括但不限于 UTF8,Unicode,ASCII,

(5)id 是記錄同一物件的唯一識別符號,

  • Line 288:若物件為基元型別,且具有一定的編碼型別,則按斬訓元型別的操作進行序列化,

注意到,除了基元型別外,還包括其他 4 種型別,也被歸于初始型別(primtiveType),

其中的 Write_xxx() 方法,此處以 Write_string 為例:

其內部的陳述句以及呼叫的方法,對檔案寫入后,就是我們在檔案中看到的內容,寫入的內容包括編碼型別、物件與其內部元素的資料型別、元素間的關系、物件當前狀態等,礙于篇幅,在此不作展開,

  • Line 292:若物件不是基元型別 + 額外增添的 4 種型別,且是基于或需要使用反射的,則利用反射進行序列化,

類 ReflectionXmlSerializationWriter,派生自類 XmlSerializationWriter,該基類有兩個子類,另一個是 XmlSerializationPrimitiveWriter,也是用來進行序列化操作,由此可知,基類 XmlSerializationWriter 相當于用來提供不同實作形式的序列化器,

對于 XmlMapping,其原理類似于字典的形式,將不同型別的元素與序列化方式一一對應做出映射,根據映射規則執行不同的序列化操作與反序列化操作,

  • Line 296:若物件有關反射的資訊為 null 或在此之前已經進行過 Xml 序列化操作,則定義一個新的并利用現有的資訊直接初始化序列化器,如果內部元素不為空,則轉到標簽 IL_D6,否則執行方法 InvokeWriter(),該方法是一種基于 Xml 的 Soap 的序列化方法,
  • Line 322:方法 Flush(),把寫在緩沖區的內容寫入檔案,清理當前撰寫器的所有緩沖區,并使所有緩沖資料寫入基礎流,

區別于方法 Close():暫時關閉,關閉當前流并釋放與之關聯的所有資源(如套接字和檔案句柄),不直接呼叫此方法,而應確保流得以正確釋放,

區別于方法 Dispose():清理記憶體,釋放某一物件使用的所有資源,Dispose 會負責 Close 的一切事務,額外還有銷毀物件的作業,即Dispose包含Close,

一般我們使用 StreamWriter 等類時,先呼叫 Flush() 將資料寫入檔案,再呼叫 Dispose() 銷毀流物件,

反序列化程序區別不大,對不同資料型別采用不同的方法,最后回傳一個型別為 object 的物件,

小結二

1. 總結一下 Xml 序列化的流程:根據物件的不同型別,采取不同的標記方式,并寫入檔案;反序列化就直接從字串中讀取買個標記塊并恢復為物件,

2. 其因為不需要對結果進行復制儲存操作,因此在效率上比二進制更快;但由于對物件中的每一個元素都要進行相應的字串標記,因此生成的檔案會大很多,這也導致了在傳輸程序中浪費資源,

3. 雖然其生成的結果檔案很大,但其可指定元素或特性的名稱,且檔案可讀性高,以及物件共享和使用的靈活性,XML 序列化將物件的公共欄位和屬性或方法的引數和回傳值序列化成符合特定XML格式的流,只要生成的XML流符合給定的架構,則對于所開發的應用程式就沒有約束,

4. 不過,其不如二進制序列化更廣泛,,序列化資料只包含資料本身以及類的結構,不包括型別標識和程式集資訊;類必須有一個將由 XmlSerializer 序列化的默認建構式,且只能序列化公共屬性和欄位,不能序列化方法、索引器、私有欄位或只讀屬性(只讀集合除外)

(三) JSON 序列化

1.  基本資訊

位于程式集 System.Text.Json.dll,命名空間 System.Text.Json 中,

—— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— ——

同樣沒有繼承任何類與介面,也是通過自定義序列化與反序列化方法,進行多次多載,

—— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— ——

共 6 個欄位

這六個欄位均為內部只讀欄位,用于在不同情況下,選用不同的標識,以完成相應的序列化操作,

  • Line 2089、2097、2106:PropertyName 直譯是“屬性名稱”,
  • Line 2118、2121、2124:metadata 直譯是“元資料”,其中,結構體 JsonEncodedText 提供將 UTF-8 或 UTF-16 編碼文本轉換為適用于 JSON 的表單的方法,此型別可用于快取和存盤用于提前撰寫 JSON 的已知字串,方法是預先對其進行編碼,Encode() 方法是將指定型別的文本轉換為 JSON 字串,即序列化后的結果表現形式,

根據欄位的前綴可以推測 s_id 表示給物件的唯一識別符號;s_ref 表示參考地址;s_values 表示物件值,

2.  序列化流程

【注:由于存在多個多載方法,此處分析的是前文(第二部分第(四)點 JSON 序列化)所呼叫的序列化方法】

  • Line 3:先來看看這個特性 RequiresUnreferencedCode 剪裁警告,

剪裁:將打包的應用取出某一部分,單獨使用,

在發布應用程式時,.NET SDK 會分析整個應用程式并洗掉所有未使用的代碼,但可能很難確定什么是未使用的,或者更準確地說是使用了什么,為了防止剪裁應用程式時行為發生變化,.NET SDK 通過“剪裁警告”提供剪裁兼容性的靜態分析,當剪裁器發現可能與剪裁不兼容的代碼時,剪裁器會生成剪裁警告, 與剪裁不兼容的代碼可能會在剪裁后的應用程式中產生行為變更,甚至崩潰,理想情況下,所有使用剪裁的應用程式都不應有剪裁警告,如果有任何剪裁警告,則應在剪裁后徹底測驗應用,以確保沒有行為變更,

  • Line 4:注意到該方法為泛型方法,其中泛型型別可空,
  • Line 4:utf8Json 表示序列化后輸入輸出的流資料型別(在之前的演示中,采用的是檔案流 FileStream);value 為待序列化物件;options 表示 Json 序列化操作的某些特定選項,默認為 null,
  • Line 10:類 Type 在之前提到過,用于存盤物件的相關資訊,

該方法用于將物件轉換為某種特定的統一型別,以便后面序列化使用,

  • Line 11:類 JsonTypeInfo,提供有關型別的 JSON 序列化相關元資料,方法 GetType() 根據不同的 options 針對剛才轉換后的物件 runtimeType 獲取其內部詳細資訊,
  • Line 12:正式開始序列化,

  • Line 1923:類 JsonSerializerOptions,提供與 JsonSerializer 一起使用的選項,此處獲取 JsonSerializerOptions 與當前JsonTypeInfo實體關聯的值,
  • Line 1924:結構體 JsonWriterOptions,允許用戶在使用 Utf8JsonWriter 撰寫 JSON 時定義自定義行為,此處保存 options 中對于寫入的行為規則(即,方式),
  • Line 1925:類 PooledByteBufferWriter,繼承了介面 IBufferWriter<byte>,表示可以向其中寫入byte 資料的一個輸出接收器;初始化大小為默認緩沖器 Buffer 的大小,其中,具體值為整型16384,
  • Line 1927:類 Utf8JsonWriter,提供高性能的 API,以便提供 UTF-8 編碼 JSON 文本的只進和非快取撰寫權限,以無快取的形式順序寫入文本,默認情況下遵循 JSON RFC,但撰寫注釋除外,此處,使用要寫入輸出的指定流和自定義選項初始化 Utf8JsonWriter 類的新實體,
  • Line 1929:結構體 WriteStack,相當于一個寫入器,將待序列化元素依次通過流寫入檔案,
  • Line 1930:類 JsonConverter,用于將物件或值轉換為 JSON,或是從 JSON 轉換為物件或值,此處存盤寫入器的初始狀態,將待序列化物件放入堆疊中,

  • Line 1934:根據緩沖器的容量計算出一個標稱值,表示當前堆疊頂的物件(當前待序列化的物件),
  • Line 1935:判斷當前堆疊是否為空,是否可以繼續進行序列化,
  • Line 1936:以 utf8 的形式將當前物件進行序列化,

  • Line 1937:清空當前臨時變數中的物件,獲取下一個物件,繼續重復直到堆疊中沒有元素,

小結三

1. 總結流程:判斷是否有特殊需求(options),獲取資訊,壓入堆疊依次遍歷寫入流,

2. 其不需要大量的注釋性字串,只保留關鍵資訊,因此資料格式比較簡單, 易于讀寫, 格式都是壓縮的, 占用帶寬小;檔案大小比 XML 序列化小很多,和二進制序列化差別不大,

3. 時間方面,其不需要像二進制序列化一樣進行過長的前搖以及頻繁的陣列復制,因此時間上比較快,但對資料的描述性比XML較差,

4. 對于二進制序列化和 XML,其實生成的結果更加易讀、更便于肉眼檢查,

5. JSON 格式支持多種語言;能夠直接為服務器端代碼使用,大大簡化了服務器端和客戶端的代碼開發量,但是完成的任務不變,且易于維護,

6. 目前,在 C# 中JSON 序列化有三種形式使用 DataContractJsonSerialize r類、使用 JavaScriptSerialize r類、使用 JSON.NET 類別庫,具體詳細資訊在此暫不做解釋,在此僅簡要說明三種方式優缺點:

(1)DataContract 和 Newtonsoft.Json 這兩種方式效率差別不大,隨著數量的增加 JavaScriptSerializer 的效率相對來說會低些,反序列化和其他兩種相差不大,,

(2)對于 DataTabl e的序列化,如果要使用 Json 資料通信,使用 Newtonsoft.Json 更合適;如果是用 XML 做持久化,使用 DataContract 合適,

(3)在容錯方便,還是 Newtonsoft.Json 比較強,

【有關 C# 中的 Stream】

【參考文獻:Stream 類 (System.IO) | Microsoft Learn && C# 溫故而知新:Stream篇(—) - 逆時針の風 - 博客園 (cnblogs.com)

【注:礙于篇幅在此僅對該內容作簡要說明,更多詳細內容請參閱 Stream 類 (System.IO) | Microsoft Learn

一、相關基礎概念

1. 流:提供位元組序列的一般視圖,

2. 位元組序列:位元組物件都被存盤為連續的位元組序列,位元組按照一定的順序進行排序組成了位元組序列,

那么流就可以稱為:供位元組序列流動的通道,在程式中反應為將物件排列起來,順序流向(放到)記憶體、檔案等地方,

二、 類 Stream

一個抽象類,繼承了類 MarshalByRefObject,兩個介面 IDisposable,IAsyncDisposable,

其中,類 MarshalByRefObject 用于允許在支持遠程處理的應用程式中跨應用程式域邊界訪問物件,簡單來說是跨區域訪問的;介面 IDisposable 用于自動析構物件,自動釋放非托管資源;IAsyncDisposable 提供一種用于異步釋放非托管資源的機制,

—— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— ——

(一) 八個屬性

1. 只讀的 Can 家族

CanRead:判斷該流是否能夠讀取;

CanSeek:判斷該流是否支持跟蹤查找;

CanWrite:判斷當前流是否可寫;

CanTimeOut 獲取一個值,該值確定當前流是否可以超時,如果網路連接中斷或丟失,會超時;如果要實作的流必須能夠超時,則應重寫此屬性以回傳 true,

2. Length

表示流的長度(以位元組為單位),

3. Position

獲取或設定當前流中的位置,

雖然從字面中可以看出這個 Position 屬性只是標示了流中的一個位置而已,可是在實際開發中會發現,在很多ASP,NET 專案中上傳檔案或圖片時,會經歷過這樣一個痛苦:Stream物件被快取了,導致了 Position 屬性在流中無法找到正確的位置,因此每次使用流前必須將 Stream.Position 設定成0,但是這還不能根本上解決問題,最好的方法就是用 Using 陳述句將流物件包裹起來,用完后關倍訓收即可,

4. Timeout 家族

獲取或設定一個值(以毫秒為單位),該值確定流在超時前將嘗試讀取/寫入的時間,如果流不支持超時,則此屬性應引發例外,

(三) 常用方法

1. Write()

向當前流中寫入位元組序列,并將此流中的當前位置提升寫入的位元組數,(依次寫入)

buffer 陣串列示此方法將 count 個位元組從 buffer 復制到當前流;

offset 表示 buffer 中的從零開始的位元組偏移量,從此處開始將位元組復制到當前流;

count 為要寫入當前流的位元組數,

ReadOnlySpan<Byte> buffer 表示一個記憶體的區域,此方法將此區域的內容復制到當前流,

2. Read()

從當前流讀取位元組序列,并將此流中的位置提升讀取的位元組數,(依次讀取)

buffer 陣列,當此方法回傳時,此緩沖區包含指定的字符陣列,此陣列中 offset 和 (offset + count - 1) 之間的值被從當前源中讀取的位元組所替換;

offset 表示 buffer 中的從零開始的位元組偏移量,從此處開始存盤從當前流中讀取的資料;

count 要從當前流中最多讀取的位元組數,

ReadOnlySpan<Byte> buffer 表示一個記憶體的區域,當此方法回傳時,此區域的內容將替換為從當前源讀取的位元組,

3. Seek()

設定當前流中的位置,

還記得Position屬性么?其實Seek方法就是重新設定流中的一個位置:如果 offset 為負,則要求新位置位于 origin 指定的位置之前,其間隔相差 offset 指定的位元組數;如果 offset 為零,則要求新位置位于由 origin 指定的位置處;如果 offset 為正,則要求新位置位于 origin 指定的位置之后,其間隔相差 offset 指定的位元組數,如:

Stream. Seek(-3, Origin.End);  表示在流末端往前數第3個位置,

Stream. Seek(0, Origin.Begin); 表示在流的開頭位置,

Stream. Seek(3, Origin.Current); 表示在流的當前位置往后數第三個位置,

4. Close()

關閉當前流并釋放與之關聯的所有資源(如套接字和檔案句柄), 不直接呼叫此方法,而應確保流得以正確釋放,

此方法呼叫方法 Dispose(),指定 true 以釋放所有資源,注意,在流關閉后嘗試操作流可能會引發 ObjectDisposedException;不關閉流可能導致資料被篡改或丟失,

總結

Stream 是所有流的抽象基類,流是位元組序列的抽象,例如檔案、輸入/輸出設備、行程中通信管道或 TCP/IP 套接字等,Stream 類及其派生類提供這些不同型別的輸入和輸出的一般視圖(方法/途徑),并將程式員與作業系統和基礎設備的具體詳細資訊隔離開來,

流涉及三個基本操作:

  • 可以從流中讀取,讀取是將資料從流傳輸到資料結構(如位元組陣列)中,
  • 可以寫入流,寫入是指將資料從資料結構傳輸到流中,
  • 流可以支持查找,查找是指查詢和修改流中的當前位置, 查找功能取決于流具有的后備存盤的型別,例如,網路流沒有當前位置的統一概念,因此通常不支持查找,

【有關 C# 中的反射 Reflection】

【參考文獻:反射 (C#) | Microsoft Learn && [整理]C#反射(Reflection)詳解 - SamWang - 博客園 (cnblogs.com)

【注:礙于篇幅在此僅對該內容作簡要說明,更多詳細內容請參閱 反射 (C#) | Microsoft Learn && C# 反射(Reflection) | 菜鳥教程 (runoob.com)

一、反射的基本概念

用ILDasm工具瀏覽一個dll和exe的構成,這種機制稱為反射,這是 .Net 中獲取運行時型別資訊的方式,它用于在運行時通過編程方式獲得型別資訊,反射可以獲取已加載的程式集和在其中定義的型別(如類、介面和值型別)資訊,也可以使用反射在運行時創建型別實體,以及呼叫和訪問這些實體,反射的一個主要功能就是查找程式集的資訊

二、運行時資訊的作用

舉個例子來說明,很多開發者喜歡在自己的軟體中留下一些介面,其他人可以撰寫一些插件來擴充軟體的功能,比如有一個媒體播放器,我希望以后可以很方便的擴展識別的格式,那么我宣告一個介面,這個介面中包含一個Extension屬性,這個屬性回傳支持的擴展名,另一個方法回傳一個解碼器的物件(這里假設了一個 Decoder 的類,這個類提供把檔案流解碼的功能,擴展插件可以派生之),通過解碼器物件我就可以解釋檔案流,

那么規定所有的解碼插件都必須派生一個解碼器,并且實作這個介面,在GetDecoder方法中回傳解碼器物件,并且將其型別的名稱配置到我的組態檔里面,

這樣的話,我就不需要在開發播放器的時侯知道將來擴展的格式的型別,只需要從組態檔中獲取現在所有解碼器的型別名稱,而動態的創建媒體格式的物件,將其轉換為 IMediaFormat介面來使用,

三、優缺點

優點:

1. 反射提高了程式的靈活性和擴展性,

2. 降低耦合性,提高自適應能力,

3. 它允許程式創建和控制任何類的物件,無需提前硬編碼目標類,

缺點:

1. 性能問題:使用反射基本上是一種解釋操作,用于欄位和方法接入時要遠慢于直接代碼,因此反射機制主要應用在對靈活性和拓展性要求很高的系統框架上,普通程式不建議使用,

2. 使用反射會模糊程式內部邏輯;程式員希望在源代碼中看到程式的邏輯,反射卻繞過了源代碼的技術,因而會帶來維護的問題,反射代碼比相應的直接代碼更復雜,

轉載請註明出處,本文鏈接:https://www.uj5u.com/net/536748.html

標籤:.NET技术

上一篇:.NET 向量型別的運算結果范例——用于學習Vector類所提供百多個向量方法

下一篇:WPF自定義控制元件之訊息提示

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • WebAPI簡介

    Web體系結構: 有三個核心:資源(resource),URL(統一資源識別符號)和表示 他們的關系是這樣的:一個資源由一個URL進行標識,HTTP客戶端使用URL定位資源,表示是從資源回傳資料,媒體型別是資源回傳的資料格式。 接下來我們說下HTTP. HTTP協議的系統是一種無狀態的方式,使用請求/ ......

    uj5u.com 2020-09-09 22:07:47 more
  • asp.net core 3.1 入口:Program.cs中的Main函式

    本文分析Program.cs 中Main()函式中代碼的運行順序分析asp.net core程式的啟動,重點不是剖析原始碼,而是理清程式開始時執行的順序。到呼叫了哪些實體,哪些法方。asp.net core 3.1 的程式入口在專案Program.cs檔案里,如下。ususing System; us ......

    uj5u.com 2020-09-09 22:07:49 more
  • asp.net網站作為websocket服務端的應用該如何寫

    最近被websocket的一個問題困擾了很久,有一個需求是在web網站中搭建websocket服務。客戶端通過網頁與服務器建立連接,然后服務器根據ip給客戶端網頁發送資訊。 其實,這個需求并不難,只是剛開始對websocket的內容不太了解。上網搜索了一下,有通過asp.net core 實作的、有 ......

    uj5u.com 2020-09-09 22:08:02 more
  • ASP.NET 開源匯入匯出庫Magicodes.IE Docker中使用

    Magicodes.IE在Docker中使用 更新歷史 2019.02.13 【Nuget】版本更新到2.0.2 【匯入】修復單列匯入的Bug,單元測驗“OneColumnImporter_Test”。問題見(https://github.com/dotnetcore/Magicodes.IE/is ......

    uj5u.com 2020-09-09 22:08:05 more
  • 在webform中使用ajax

    如果你用過Asp.net webform, 說明你也算是.NET 開發的老兵了。WEBform應該是2011 2013左右,當時還用visual studio 2005、 visual studio 2008。后來基本都用的是MVC。 如果是新開發的專案,估計沒人會用webform技術。但是有些舊版 ......

    uj5u.com 2020-09-09 22:08:50 more
  • iis添加asp.net網站,訪問提示:由于擴展配置問題而無法提供您請求的

    今天在iis服務器配置asp.net網站,遇到一個問題,記錄一下: 問題:由于擴展配置問題而無法提供您請求的頁面。如果該頁面是腳本,請添加處理程式。如果應下載檔案,請添加 MIME 映射。 WindowServer2012服務器,添加角色安裝完.netframework和iis之后,運行aspx頁面 ......

    uj5u.com 2020-09-09 22:10:00 more
  • WebAPI-處理架構

    帶著問題去思考,大家好! 問題1:HTTP請求和回傳相應的HTTP回應資訊之間發生了什么? 1:首先是最底層,托管層,位于WebAPI和底層HTTP堆疊之間 2:其次是 訊息處理程式管道層,這里比如日志和快取。OWIN的參考是將訊息處理程式管道的一些功能下移到堆疊下端的OWIN中間件了。 3:控制器處理 ......

    uj5u.com 2020-09-09 22:11:13 more
  • 微信門戶開發框架-使用指導說明書

    微信門戶應用管理系統,采用基于 MVC + Bootstrap + Ajax + Enterprise Library的技術路線,界面層采用Boostrap + Metronic組合的前端框架,資料訪問層支持Oracle、SQLServer、MySQL、PostgreSQL等資料庫。框架以MVC5,... ......

    uj5u.com 2020-09-09 22:15:18 more
  • WebAPI-HTTP編程模型

    帶著問題去思考,大家好!它是什么?它包含什么?它能干什么? 訊息 HTTP編程模型的核心就是訊息抽象,表示為:HttPRequestMessage,HttpResponseMessage.用于客戶端和服務端之間交換請求和回應訊息。 HttpMethod類包含了一組靜態屬性: private stat ......

    uj5u.com 2020-09-09 22:15:23 more
  • 部署WebApi隨筆

    一、跨域 NuGet參考Microsoft.AspNet.WebApi.Cors WebApiConfig.cs中配置: // Web API 配置和服務 config.EnableCors(new EnableCorsAttribute("*", "*", "*")); 二、清除默認回傳XML格式 ......

    uj5u.com 2020-09-09 22:15:48 more
最新发布
  • C#多執行緒學習(二) 如何操縱一個執行緒

    <a href="https://www.cnblogs.com/x-zhi/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2943582/20220801082530.png" alt="" /></...

    uj5u.com 2023-04-19 09:17:20 more
  • C#多執行緒學習(二) 如何操縱一個執行緒

    C#多執行緒學習(二) 如何操縱一個執行緒 執行緒學習第一篇:C#多執行緒學習(一) 多執行緒的相關概念 下面我們就動手來創建一個執行緒,使用Thread類創建執行緒時,只需提供執行緒入口即可。(執行緒入口使程式知道該讓這個執行緒干什么事) 在C#中,執行緒入口是通過ThreadStart代理(delegate)來提供的 ......

    uj5u.com 2023-04-19 09:16:49 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    <a href="https://www.cnblogs.com/huangxincheng/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/214741/20200614104537.png" alt="" /&g...

    uj5u.com 2023-04-18 08:39:04 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    一:背景 1. 講故事 前段時間協助訓練營里的一位朋友分析了一個程式卡死的問題,回過頭來看這個案例比較經典,這篇稍微整理一下供后來者少踩坑吧。 二:WinDbg 分析 1. 為什么會卡死 因為是表單程式,理所當然就是看主執行緒此時正在做什么? 可以用 ~0s ; k 看一下便知。 0:000> k # ......

    uj5u.com 2023-04-18 08:33:10 more
  • SignalR, No Connection with that ID,IIS

    <a href="https://www.cnblogs.com/smartstar/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/u36196.jpg" alt="" /></a>...

    uj5u.com 2023-03-30 17:21:52 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:15:33 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:13:31 more
  • C#遍歷指定檔案夾中所有檔案的3種方法

    <a href="https://www.cnblogs.com/xbhp/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/957602/20230310105611.png" alt="" /></a&...

    uj5u.com 2023-03-27 14:46:55 more
  • C#/VB.NET:如何將PDF轉為PDF/A

    <a href="https://www.cnblogs.com/Carina-baby/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2859233/20220427162558.png" alt="" />...

    uj5u.com 2023-03-27 14:46:35 more
  • 武裝你的WEBAPI-OData聚合查詢

    <a href="https://www.cnblogs.com/podolski/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/616093/20140323000327.png" alt="" /><...

    uj5u.com 2023-03-27 14:46:16 more