3萬字長文概述:通俗易懂告訴你什麼是.NET?什麼是.NET Framework?什麼是.NET Core?

【轉載】通俗易懂,什麼是.NET?什麼是.NET Framework?什麼是.NET Core?

什麼是.NET?什麼是.NET Framework?本文將從上往下,按部就班的介紹一系列相關.NET的概念,先從類型系統開始講起,我將經過跨語言操做這個例子來逐漸引入一系列.NET的相關概念,這主要包括:CLS、CTS(CLI)、FCL、Windows下CLR的相關核心組成、Windows下託管程序運行概念、什麼是.NET Framework,.NET Core,.NET Standard及一些VS編譯器相關雜項和相關閱讀連接。完整的從上讀到下則你能夠理解個大概的.NET體系。html

文章是我一字一字親手碼出來的,天天下班用休息時間寫一點,持續了二十來天。且對於文章上下銜接、概念引入花了不少心思,致力讓不少概念在本文中顯得通俗。但畢竟.NET系統很龐大,本文篇幅有限,因此在部分小節中我會給出延伸閱讀的連接,在文章結尾我給出了一些小的建議,但願能對須要幫助的人帶來幫助,若是想與我交流能夠文章留言或者加.NET技術交流羣:166843154git

目錄github

.NET和C#是什麼關係

語言,是人們進行溝通表達的主要方式。編程語言,是人與機器溝通的表達方式。不一樣的編程語言,其側重點不一樣。有的編程語言是爲了科學計算而開發的,因此其語法和功能更偏向於函數式思想。有些則是爲了開發應用程序而創立的,因此其語法和功能更爲均衡全面。數據庫

微軟公司是全球最大的電腦軟件提供商,爲了佔據開發者市場,進而在2002年推出了Visual Studio(簡稱VS,是微軟提供給開發者的工具集) .NET 1.0版本的開發者平臺。而爲了吸引更多的開發者涌入平臺,微軟還在2002年宣佈推出一個特性強大而且與.NET平臺無縫集成的編程語言,即C# 1.0正式版。
只要是.NET支持的編程語言,開發者就能夠經過.NET平臺提供的工具服務和框架支持便捷的開發應用程序。編程

C#就是爲宣傳.NET而創立的,它直接集成於Visual Studio .NET中,VB也在.NET 1.0發佈後對其進行支持, 因此這兩門語言與.NET平臺耦合度很高,而且.NET上的技術大多都是以C#編程語言爲示例,因此常常就.NET和C#混爲一談(實質上它們是相輔相成的兩個概念)。
而做爲一個開發者平臺,它不只僅是包含開發環境、技術框架、社區論壇、服務支持等,它還強調了平臺的跨語言、跨平臺編程的兩個特性。json

跨語言和跨平臺是什麼

跨語言:即只要是面向.NET平臺的編程語言((C#、Visual Basic、C++/CLI、Eiffel、F#、IronPython、IronRuby、PowerBuilder、Visual COBOL 以及 Windows PowerShell)),用其中一種語言編寫的類型能夠無縫地用在另外一種語言編寫的應用程序中的互操做性。
跨平臺:一次編譯,不須要任何代碼修改,應用程序就能夠運行在任意有.NET框架實現的平臺上,即代碼不依賴於操做系統,也不依賴硬件環境。c#

什麼是跨語言互操做,什麼是CLS

每門語言在最初被設計時都有其在功能和語法上的定位,讓不一樣的人使用擅長的語言去幹合適的事,這在團隊協做時尤其重要。
.NET平臺上的跨語言是經過CLS這個概念來實現的,接下來我就以C#和VB來演示 什麼是.NET中的跨語言互操做性。

通俗來講,雖然c#和vb是兩個不一樣的語言,但此處c#寫的類能夠在vb中當作自家寫的類同樣正常使用。

好比我在vb中寫了一個針對String的首字母大寫的擴展方法,將其編譯後的dll引用至C#項目中。

在C#項目中,能夠像自身代碼同樣正常使用來自vb這個dll的擴展方法。

如今有那麼多面向對象語言,但不是全部編程語言都能這樣直接互操做使用,而.NET平臺支持的C#和VB之因此能這樣無縫銜接,先讀然後知,後文將會介紹原因。不過雖然.NET平臺提供了這樣一個互操做的特性,但終究語言是不同的,每一個語言有其特點和差別處,在相互操做的時候就會不免遇到一些例外狀況。

好比我在C#中定義了一個基類,類裏面包含一個公開的指針類型的成員,我想在vb中繼承這個類,並訪問這個公開的成員。

可是vb語言由於其定位不須要指針,因此並無C#中如int*這樣的指針類型,因此在vb中訪問一個該語言不支持的類型會報錯的,會提示:字段的類型不受支持。

再好比,C#語言中,對類名是區分大小寫的,我在C#中定義了兩個類,一個叫BaseBusiness,另外一個叫baseBusiness。我在vb中去繼承這個BaseBusiness類。

如圖,在vb中訪問這個類會報錯的,報:"BaseBusiness"不明確,這是由於在vb中對類名是不區分大小寫的。在vb中,它認爲它同時訪問了兩個如出一轍的類,因此按照vb的規則這是不合理的。那麼爲了在vb調用c#的程序集中避免這些因語言的差別性而致使的錯誤,在編寫c#代碼的時候 就應該提早知道vb中的這些規則,來應付式的開發。

可是,若是我想不只僅侷限於C#和VB,我還想我編寫的代碼在.Net平臺上通用的話,那麼我還必須得知道.NET平臺支持的每一種語言和我編寫代碼所使用的語言的差別,從而在編寫代碼中避免這些。

這幾年編程語言層出不窮,在未來.NET可能還會支持更多的語言,若是說對一個開發者而言掌握全部語言的差別處這是不現實的,因此.NET專門爲此參考每種語言並找出了語言間的共性,而後定義了一組規則,開發者都遵照這個規則來編碼,那麼代碼就能被任意.NET平臺支持的語言所通用。
而與其說是規則,不如說它是一組語言互操做的標準規範,它就是公共語言規範 - Common Language Specification ,簡稱CLS

CLS從類型、命名、事件、屬性、數組等方面對語言進行了共性的定義及規範。這些東西被提交給歐洲計算機制造聯合會ECMA,稱爲:共同語言基礎設施。

就以類型而言,CLS定義了在C#語言中符合規範的類型和不符合的有:

固然,就編碼角度而言,咱們不是必需要看那些詳略的文檔。爲了方便開發者開發,.NET提供了一個特性,名叫:CLSCompliantAttribute,代碼被CLSCompliantAttribute標記後,若是你寫的代碼不符合CLS規範的話,編譯器就會給你一條警告。

值得一提的是,CLS規則只是面向那些公開可被其它程序集訪問的成員,如public、繼承的protected,對於該程序集的內部成員如Private、internal則不會執行該檢測規則。也就是說,所適應的CLS聽從性規則,僅是那些公開的成員,而非私有實現。

那麼有沒有那種特殊狀況,好比我經過反射技術來訪問該程序集中,當前語言並不擁有的類型時會發生什麼狀況呢?

答案是能夠嘗試的,如用vb反射訪問c#中的char*指針類型,即便vb中沒有char*這種等價的指針類型,但mscorlib提供了針對指針類型的 Pointer 包裝類供其訪問,能夠從運行時類攜帶的類型名稱看到其本來的類型名。

能夠看到,該類中的元素是不符合CLS規範的。

CLS異常

提到特殊狀況,還要說的一點就是異常處理。.NET框架組成中定義了異常類型系統,在編譯器角度,全部catch捕獲的異常都必須繼承自System.Exception,若是你要調用一個 由不遵循此規範的語言 拋出其它類型的異常對象(C++容許拋出任何類型的異常,如C#調用C++代碼,C++拋出一個string類型的異常),在C#2.0以前Catch(Exception)是捕捉不了的,但以後的版本能夠。
在後續版本中,微軟提供了System.Runtime.CompilerServices.RuntimeWrappedException異常類,將那些不符合CLS的包含Exception的對象封裝起來。而且能夠經過RuntimeCompatibilityAttribute特性來過濾這些異常。
RuntimeWrappedException :https://docs.microsoft.com/zh-cn/dotnet/api/system.runtime.compilerservices.runtimewrappedexception?view=netframework-4.7.2

那麼,這個段落總結一下,什麼是CLS呢?

在面向.NET開發中,編寫跨語言組件時所遵循的那些共性,那些規範就叫作 Common Langrage Specification簡稱 CLS,公共語言規範
官方CLS介紹:https://docs.microsoft.com/zh-cn/dotnet/standard/language-independence-and-language-independent-components

什麼是CTS?

若是理解了什麼是CLS的話,那麼你將很輕鬆理解什麼是CTS。
假設你已經圍繞着封裝 繼承 多態 這3個特性設計出了多款面向對象的語言,你發現你們都是面向對象,都能很好的將現實中的對象模型表達出來。除了語法和功能擅長不一樣,語言的定義和設計結構其實都差很少一回事。

好比,現實中你看到了一輛小汽車,這輛車裏坐着兩我的,那麼如何用這門語言來表達這樣的一個概念和場面?
首先要爲這門語言橫向定義一個「類型」的概念。接下來在程序中就能夠這樣表示:有一個汽車類型,有一我的類型,在一個汽車類型的對象內包含着兩我的類型的對象,由於要表達出這個模型,你又引入了「對象」的概念 。而如今,你又看到,汽車裏面的人作出了開車的這樣一個動做,由此你又引入了「動做指令」這樣一個概念。
接着,你又恍然大悟總結出一個定理,不管是什麼樣的「類型」,都只會存在這樣一個特徵,即活着的 帶生命特徵的(如人) 和 死的 沒有生命特徵的(如汽車) 這二者中的一個。最後,隨着思想模型的成熟,你發現,這個「類型」就至關於一個富有主體特徵的一組指令的集合。
好,而後你開始照葫蘆畫瓢。你參考其它程序語言,你發現你們都是用class來表示類的含義,用struct表示結構的含義,用new來表示 新建一個對象的含義,因而,你對這部分功能的語法也使用class和new關鍵字來表示。而後你又發現,他們還用不少關鍵字來更豐富的表示這些現實模型,好比override、virtual等。因而,在不斷的思想升級和借鑑後,你對這個設計語言過程當中思想的變化仔細分析,對這套語言體系給抽象概括,最終總結出一套體系。

因而你對其它人這樣說,我總結出了一門語言不少必要的東西如兩種主要類別:值類別和引用類別,五個主要類型:類、接口、委託、結構、枚舉,我還規定了,一個類型能夠包含字段、屬性、方法、事件等成員,我還指定了每種類型的可見性規則和類型成員的訪問規則,等等等等,只要按照我這個體系來設計語言,設計出來的語言它可以擁有不少不錯的特性,好比跨語言,跨平臺等,C#和VB.net之因此可以這樣就是由於這兩門語言的設計符合我這個體系。

那麼,什麼是CTS呢?

當你須要設計面向.Net的語言時所須要遵循一個體系(.Net平臺下的語言都支持的一個體系)這個體系就是CTS(Common Type System 公共類型系統),它包括但不限於:

  • 創建用於跨語言執行的框架。
  • 提供面向對象的模型,支持在 .NET 實現上實現各類語言。
  • 定義處理類型時全部語言都必須遵照的一組規則(CLS)。
  • 提供包含應用程序開發中使用的基本基元數據類型(如 Boolean、Byte、Char 等)的庫。

上文的CLS是CTS(Common Type System 公共類型系統)這個體系中的子集。
一個編程語言,若是它可以支持CTS,那麼咱們就稱它爲面向.NET平臺的語言。
官方CTS介紹: https://docs.microsoft.com/zh-cn/dotnet/standard/common-type-system

微軟已經將CTS和.NET的一些其它組件,提交給ECMA以成爲公開的標準,最後造成的標準稱爲CLI(Common Language Infrastructure)公共語言基礎結構。
因此有的時候你見到的書籍或文章有的只提起CTS,有的只提起CLI,請不要奇怪,你能夠寬泛的把他們理解成一個意思,CLI是微軟將CTS等內容提交給國際組織計算機制造聯合會ECMA的一個工業標準。

什麼是類庫?

在CTS中有一條就是要求基元數據類型的類庫。咱們先搞清什麼是類庫?類庫就是類的邏輯集合,你開發工做中你用過或本身編寫過不少工具類,好比搞Web的常常要用到的 JsonHelper、XmlHelper、HttpHelper等等,這些類一般都會在命名爲Tool、Utility等這樣的項目中。 像這些類的集合咱們能夠在邏輯上稱之爲 "類庫",好比這些Helper咱們統稱爲工具類庫。

什麼是基礎類庫BCL?

當你經過VS建立一個項目後,你這個項目就已經引用好了經過.NET下的語言編寫好的一些類庫。好比控制檯中你直接就能夠用ConSole類來輸出信息,或者using System.IO 便可經過File類對文件進行讀取或寫入操做,這些類都是微軟幫你寫好的,不用你本身去編寫,它幫你編寫了一個面向.NET的開發語言中使用的基本的功能,這部分類,咱們稱之爲BCL(Base Class Library), 基礎類庫,它們大多都包含在System命名空間下。

基礎類庫BCL包含:基本數據類型,文件操做,集合,自定義屬性,格式設置,安全屬性,I/O流,字符串操做,事件日誌等的類型

什麼是框架類庫FCL?

有關BCL的就不在此一一類舉。.NET之大,發展至今,由微軟幫助開發人員編寫的類庫愈來愈多,這讓咱們開發人員開發更加容易。由微軟開發的類庫統稱爲:FCL,Framework Class Library ,.NET框架類庫,我上述所表達的BCL就是FCL中的一個基礎部分,FCL中大部分類都是經過C#來編寫的。

在FCL中,除了最基礎的那部分BCL以外,還包含咱們常見的 如 : 用於網站開發技術的 ASP.NET類庫,該子類包含webform/webpage/mvc,用於桌面開發的 WPF類庫、WinForm類庫,用於通訊交互的WCF、asp.net web api、Web Service類庫等等

什麼是基元類型?

像上文在CTS中提到了 基本基元數據類型,你們知道,每門語言都會定義一些基礎的類型,好比C#經過 int 來定義整型,用 string 來定義 字符串 ,用 object 來定義 根類。當咱們來描述這樣一個類型的對象時能夠有這兩種寫法,如圖:

咱們能夠看到,上邊用首字母小寫的藍色體string、object能描述,用首字母大寫的淺藍色String、Object也能描述,這兩種表述方式有何不一樣?

要知道,在vs默認的顏色方案中,藍色體 表明關鍵字,淺藍色體 表明類型。
那麼這樣也就意味着,由微軟提供的FCL類庫裏面 包含了 一些用於描述數據類型的 基礎類型,不管咱們使用的是什麼語言,只要引用了FCL,咱們均可以經過new一個類的方式來表達數據類型。
如圖:

用new來建立這些類型的對象,但這樣就太繁瑣,因此C#就用 int關鍵字來表示System.Int32,用 string關鍵字來表示 System.String等,因此咱們才能這樣去寫。

像這樣被表述於編譯器直接支持的類型叫作基元類型,它被直接映射於BCL中具體的類。

下面是部分面向.NET的語言的基元類型與對應的BCL的類別圖 :

System.Object的意義

提及類型,這裏要說CTS定義的一個很是重要的規則,就是類與類之間只能單繼承,System.Object類是全部類型的根,任何類都是顯式或隱式的繼承於System.Object。

System.Object定義了類型的最基本的行爲:用於實例比較的Equals系列方法、用於Hash表中Hash碼的GetHashCode、用於Clr運行時獲取的類型信息GetType、用於表示當前對象字符串的ToString、用於執行實例的淺複製MemberwiseClone、用於GC回收前操做的析構方法Finalize 這6類方法。

因此 Object不只是C#語言的類型根、仍是VB等全部面向.NET的語言的類型根,它是整個FCL的類型根。

固然,CTS定義了單繼承,不少編程語言都知足這個規則,但也有語言是例外,如C++就不作繼承限制,能夠繼承多個,C++/CLI做爲C++在對.NET的CLI實現,若是在非託管編碼中多繼承那也能夠,若是試圖在託管代碼中多繼承,那就會報錯。我前面已經舉過這樣特殊狀況的例子,這也在另外一方面反映出,各語言對CTS的支持並非都如C#那樣全面的,咱們只需明記一點:對於符合CTS的那部分天然就按照CTS定義的規則來。 任何可遵循CTS的類型規範,同時又有.NET運行時的實現的編程語言就能夠成爲.NET中的一員。

計算機是如何運行程序的?

接下來我要說什麼是.NET的跨平臺,並解釋爲何可以跨語言。不過要想知道什麼是跨平臺,首先你得知道一個程序是如何在本機上運行的。

什麼是CPU

CPU,全稱Central Processing Unit,叫作中央處理器,它是一塊超大規模的集成電路,是計算機組成上必不可少的組成硬件,沒了它,計算機就是個殼。
不管你編程水平怎樣,你都應該先知道,CPU是一臺計算機的運算核心和控制核心,CPU從存儲器或高速緩衝存儲器中取出指令,放入指令寄存器,並對指令譯碼,執行指令。
咱們運行一個程序,CPU就會不斷的讀取程序中的指令並執行,直到關閉程序。事實上,從電腦開機開始,CPU就一直在不斷的執行指令直到電腦關機。

什麼是高級編程語言

在計算機角度,每一種CPU類型都有本身能夠識別的一套指令集,計算機無論你這個程序是用什麼語言來編寫的,其最終只認其CPU可以識別的二進制指令集。
在早期計算機剛發展的時代,人們都是直接輸入01010101這樣的沒有語義的二進制指令來讓計算機工做的,可讀性幾乎沒有,沒人願意直接編寫那些沒有可讀性、繁瑣、費時,易出差錯的二進制01代碼,因此後來纔出現了編程語言。

編程語言的誕生,使得人們編寫的代碼有了可讀性,有了語義,與直接用01相比,更有利於記憶。
而前面說了,計算機最終只識別二進制的指令,那麼,咱們用編程語言編寫出來的代碼就必需要轉換成供機器識別的指令。
就像這樣:

複製代碼

code: 1+2 function 翻譯方法(參數:code)
{
... "1"=>"001"; "2"=>"002"; "+"=>"000"; return 能讓機器識別的二進制代碼;
}
call 翻譯方法("1+2") => "001 000 002"

複製代碼

因此從一門編程語言所編寫的代碼文件轉換成能讓本機識別的指令,這中間是須要一個翻譯的過程。
而咱們如今計算機上是運載着操做系統的,光翻譯成機器指令也不行,還得讓代碼文件轉化成可供操做系統執行的程序才行。
那麼這些步驟,就是編程語言所對應的編譯環節的工程了。這個翻譯過程是須要工具來完成,咱們把它叫作 編譯器。

不一樣廠商的CPU有着不一樣的指令集,爲了克服面向CPU的指令集的難讀、難編、難記和易出錯的缺點,後來就出現了面向特定CPU的特定彙編語言, 好比我打上這樣的x86彙編指令 mov ax,bx ,而後用上用機器碼作的彙編器,它將會被翻譯成 1000100111011000 這樣的二進制01格式的機器指令.

不一樣CPU架構上的彙編語言指令不一樣,而爲了統一一套寫法,同時又不失彙編的表達能力,C語言就誕生了。
用C語言寫的代碼文件,會被C編譯器先轉換成對應平臺的彙編指令,再轉成機器碼,最後將這些過程當中產生的中間模塊連接成一個能夠被操做系統執行的程序。

那麼彙編語言和C語言比較,咱們就不須要去閱讀特定CPU的彙編碼,我只須要寫通用的C源碼就能夠實現程序的編寫,咱們用將更偏機器實現的彙編語言稱爲低級語言,與彙編相比,C語言就稱之爲高級語言。

在看看咱們C#,咱們在編碼的時候都不須要過於偏向特定平臺的實現,翻譯過程也基本遵循這個過程。它的編譯模型和C語言相似,都是屬於這種間接轉換的中間步驟,故而可以跨平臺。
因此就相似於C/C#等這樣的高級語言來講是不區分平臺的,而在於其背後支持的這個 翻譯原理 是否能支持其它平臺。

什麼是託管代碼,託管語言,託管模塊?

做爲一門年輕的語言,C#借鑑了許多語言的長處,與C比較,C#則更爲高級。
每每一段簡小的C#代碼,其功能卻至關於C的一大段代碼,而且用C#語言你幾乎不須要指針的使用,這也就意味着你幾乎不須要進行人爲的內存管控與安全考慮因素,也不須要多懂一些操做系統的知識,這讓編寫程序變得更加輕鬆和快捷。

若是說C#一段代碼能夠完成其它低級語言一大段任務,那麼咱們能夠說它特性豐富或者類庫豐富。而用C#編程不須要人爲內存管控是怎麼作到的呢?
.NET提供了一個垃圾回收器(GC)來完成這部分工做,當你建立類型的時候,它會自動給你分配所須要的這部份內存空間。就至關於,有一個專門的軟件或進程,它會讀取你的代碼,而後當你執行這行代碼的時候,它幫你作了內存分配工做。 這部分本該你作的工做,它幫你作了,這就是「託管」的概念。好比現實中 託管店鋪、託管教育等這樣的別人替你完成的概念。

所以,C#被稱之爲託管語言。C#編寫的代碼也就稱之爲託管代碼,C#生成的模塊稱之爲託管模塊等。(對於託管的資源,是不須要也沒法咱們人工去幹預的,但咱們能夠了解它的一些機制原理,在後文我會簡單介紹。)

只要有比較,就會產生概念。那麼在C#角度,那些脫離了.NET提供的諸如垃圾回收器這樣的環境管制,就是對應的 非託管了。

非託管的異常

咱們編寫的程序有的模塊是由託管代碼編寫,有的模塊則調用了非託管代碼。在.NET Framework中也有一套基於此操做系統SEH的異常機制,理想的機制設定下咱們能夠直接經過catch(e)或catch來捕獲指定的異常和框架設計人員容許咱們捕獲的異常。

而異常類型的級別也有大有小,有小到能夠直接框架自己或用代碼處理的,有大到須要操做系統的異常機制來處理。.NET會對那些能讓程序崩潰的異常類型給進行標記,對於這部分異常,在.NET Framework 4.0以前容許開發人員在代碼中本身去處理,但4.0版本以後有所變動,這些被標記的異常默認不會在託管環境中拋出(即沒法catch到),而是由操做系統的SEH機制去處理。
不過若是你仍然想在代碼中捕獲處理這樣的異常也是能夠的,你能夠對須要捕獲的方法上標記[System.Runtime.ExceptionServices.HandleProcessCorruptedStateExceptionsAttribute]特性,就能夠在該方法內經過catch捕獲到該類型的異常。你也能夠經過在配置文件中添加運行時節點來對全局進行這樣的一個配置:



HandleProcessCorruptedStateExceptions特性:https://msdn.microsoft.com/zh-cn/library/azure/system.runtime.exceptionservices.handleprocesscorruptedstateexceptionsattribute.aspx
SEHException類:https://msdn.microsoft.com/en-us/library/system.runtime.interopservices.sehexception(v=vs.100).aspx
處理損壞狀態異常博客專欄: https://msdn.microsoft.com/zh-cn/magazine/dd419661.aspx

什麼是CLR,.NET虛擬機?

實際上,.NET不只提供了自動內存管理的支持,他還提供了一些列的如類型安全、應用程序域、異常機制等支持,這些 都被統稱爲CLR公共語言運行庫。

CLR是.NET類型系統的基礎,全部的.NET技術都是創建在此之上,熟悉它能夠幫助咱們更好的理解框架組件的核心、原理。
在咱們執行託管代碼以前,總會先運行這些運行庫代碼,經過運行庫的代碼調用,從而構成了一個用來支持託管程序的運行環境,進而完成諸如不須要開發人員手動管理內存,一套代碼便可在各大平臺跑的這樣的操做。

這套環境及體系之完善,以致於就像一個小型的系統同樣,因此一般形象的稱CLR爲".NET虛擬機"。那麼,若是以進程爲最低端,進程的上面就是.NET虛擬機(CLR),而虛擬機的上面纔是咱們的託管代碼。換句話說,託管程序其實是寄宿於.NET虛擬機中。

什麼是CLR宿主進程,運行時主機?

那麼相對應的,容納.NET虛擬機的進程就是CLR宿主進程了,該程序稱之爲運行時主機。

這些運行庫的代碼,全是由C/C++編寫,具體表現爲以mscoree.dll爲表明的核心dll文件,該dll提供了N多函數用來構建一個CLR環境 ,最後當運行時環境構建完畢(一些函數執行完畢)後,調用_CorDllMain或_CorExeMain來查找並執行託管程序的入口方法(如控制檯就是Main方法)。

若是你足夠熟悉CLR,那麼你徹底能夠在一個非託管程序中經過調用運行庫函數來定製CLR並執行託管代碼。
像SqlServer就集成了CLR,能夠使用任何 .NET Framework 語言編寫存儲過程、觸發器、用戶定義類型、用戶定義函數(標量函數和表值函數)以及用戶定義的聚合函數。

有關CLR大綱介紹: https://msdn.microsoft.com/zh-cn/library/9x0wh2z3(v=vs.85).aspx
CLR集成: https://docs.microsoft.com/zh-cn/previous-versions/sql/sql-server-2008/ms131052(v%3dsql.100)
構造CLR的接口:https://msdn.microsoft.com/zh-cn/library/ms231039(v=vs.85).aspx
適用於 .NET Framework 2.0 的宿主接口:https://msdn.microsoft.com/zh-cn/library/ms164336(v=vs.85).aspx
選擇CLR版本: https://docs.microsoft.com/en-us/dotnet/framework/configure-apps/file-schema/startup/supportedruntime-element

因此C#編寫的程序若是想運行就必需要依靠.NET提供的CLR環境來支持。 而CLR是.NET技術框架中的一部分,故只要在Windows系統中安裝.NET Framework便可。

Windows系統自帶.NET Framework

Windows系統默認安裝的有.NET Framework,而且能夠安裝多個.NET Framework版本,你也不須要所以卸載,由於你使用的應用程序可能依賴於特定版本,若是你移除該版本,則應用程序可能會中斷。

Microsoft .NET Framework百度百科下有windows系統默認安裝的.NET版本

圖出自 https://baike.baidu.com/item/Microsoft%20.NET%20Framework/9926417?fr=aladdin

.NET Framework 4.0.30319

在%SystemRoot%\Microsoft.NET下的Framework和Framework64文件夾中分別能夠看到32位和64位的.NET Framework安裝的版本。
咱們點進去能夠看到以.NET版本號爲命名的文件夾,有2.0,3.0,3.5,4.0這幾個文件夾。

.NET Framework4.X覆蓋更新

要知道.NET Framework版本目前已經迭代到4.7系列,電腦上明明安裝了比4.0更高版本的.NET Framework,然而從文件夾上來看,最高不過4.0,這是爲什麼?
原來自.NET Framework 4以來的全部.NET Framework版本都是直接在v4.0.30319文件夾上覆蓋更新,而且沒法安裝之前的4.x系列的老版本,因此v4.0.30319這個目錄中其實放的是你最後一次更新的NET Framework版本。
.NET Framework覆蓋更新:https://docs.microsoft.com/en-us/dotnet/framework/install/guide-for-developers

如何確認本機安裝了哪些.NET Framework和對應CLR的版本?

咱們能夠經過註冊表等其它方式來查看安裝的最新版本:https://docs.microsoft.com/zh-cn/dotnet/framework/migration-guide/how-to-determine-which-versions-are-installed
不過若是不想那麼複雜的話,還有種最直接簡單的:
那就是進入該目錄文件夾,隨便找到幾個文件對其右鍵,而後點擊詳細信息便可查看到對應的文件版本,能夠依據文件版本估摸出.NET Framework版本,好比csc.exe文件。

什麼是程序集

上文我介紹了編譯器,即將源代碼文件給翻譯成一個計算機可識別的二進制程序。而在.NET Framework目錄文件夾中就附帶的有 用於C#語言的命令行形式的編譯器csc.exe 和 用於VB語言的命令行形式的編譯器vbc.exe。

咱們經過編譯器能夠將後綴爲.cs(C#)和.vb(VB)類型的文件編譯成程序集。
程序集是一個抽象的概念,不一樣的編譯選項會產生不一樣形式的程序集。以文件個數來區分的話,那麼就分 單文件程序集(即一個文件)和多文件程序集(多個文件)。
而不管是單文件程序集仍是多文件程序集,其總有一個核心文件,就是表現爲後綴爲.dll或.exe格式的文件。它們都是標準的PE格式的文件,主要由4部分構成:

  • 1.PE頭,即Windows系統上的可移植可執行文件的標準格式
  • 2.CLR頭,它是託管模塊特有的,它主要包括
    • 1)程序入口方法
    • 2)CLR版本號等一些標誌
    • 3)一個可選的強名稱數字簽名
    • 4)元數據表,主要用來記錄了在源代碼中定義和引用的全部的類型成員(如方法、字段、屬性、參數、事件...)的位置和其標誌Flag(各類修飾符)
      正是由於元數據表的存在,VS才能智能提示,反射才能獲取MemberInfo,CLR掃描元數據表便可得到該程序集的相關重要信息,因此元數據表使得程序集擁有了自我描述的這一特性。clr2中,元數據表大概40多個,其核心按照用途分爲3類:

    • 1.即用於記錄在源代碼中所定義的類型的定義表:ModuleDef、TypeDef、MethodDef、ParamDef、FieldDef、PropertyDef、EventDef,
    • 2.引用了其它程序集中的類型成員的引用表:MemberRef、AssemblyRef、ModuleRef、TypeRef
    • 3. 用於描述一些雜項(如版本、發佈者、語言文化、多文件程序集中的一些資源文件等)的清單表:AssemblyDef、FileDef、ManifestResourceDef、ExportedTypeDef

  • 3.IL代碼(也稱MSIL,後來被更名爲CIL:Common Intermediate Language通用中間語言),是介於源代碼和本機機器指令中間的代碼,將經過CLR在不一樣的平臺產生不一樣的二進制機器碼。
  • 4.一些資源文件

多文件程序集的誕生場景有:好比我想爲.exe綁定資源文件(如Icon圖標),或者我想按照功能以增量的方式來按需編譯成.dll文件。 一般不多狀況下才會將源代碼編譯成多文件程序集,而且在VS IDE中老是將源代碼給編譯成單文件的程序集(要麼是.dll或.exe),因此接下來我就以單文件程序集爲例來說解。

用csc.exe進行編譯

如今,我將演示一段文本是如何被csc.exe編譯成一個可執行的控制檯程序的。
咱們新建個記事本,而後將下面代碼複製上去。

using System; using System.IO; using System.Net.Sockets; using System.Text; class Program
{ static void Main()
    { string rootDirectory = Environment.CurrentDirectory;
        Console.WriteLine("開始鏈接,端口號:8090");
        Socket socket \= new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        socket.Bind(new System.Net.IPEndPoint(System.Net.IPAddress.Loopback, 8090));
        socket.Listen(30); while (true)
        {
            Socket socketClient \= socket.Accept();
            Console.WriteLine("新請求"); byte\[\] buffer = new byte\[4096\]; int length = socketClient.Receive(buffer, 4096, SocketFlags.None); string requestStr = Encoding.UTF8.GetString(buffer, 0, length);
            Console.WriteLine(requestStr); //
            string\[\] strs = requestStr.Split(new string\[\] { "\\r\\n" }, StringSplitOptions.None); string url = strs\[0\].Split(' ')\[1\]; byte\[\] statusBytes, headerBytes, bodyBytes; if (Path.GetExtension(url) == ".jpg")
            { string status = "HTTP/1.1 200 OK\\r\\n";
                statusBytes \= Encoding.UTF8.GetBytes(status);
                bodyBytes \= File.ReadAllBytes(rootDirectory + url); string header = string.Format("Content-Type:image/jpg;\\r\\ncharset=UTF-8\\r\\nContent-Length:{0}\\r\\n", bodyBytes.Length);
                headerBytes \= Encoding.UTF8.GetBytes(header);
            } else { if (url == "/")
                    url \= "默認頁"; string status = "HTTP/1.1 200 OK\\r\\n";
                statusBytes \= Encoding.UTF8.GetBytes(status); string body = "<html>" +
                    "<head>" +
                        "<title>socket webServer  -- Login</title>" +
                    "</head>" +
                    "<body>" +
                       "<div style=\\"text-align:center\\">" +
                           "當前訪問" + url +
                       "</div>" +
                    "</body>" +
                "</html>";
                bodyBytes \= Encoding.UTF8.GetBytes(body); string header = string.Format("Content-Type:text/html;charset=UTF-8\\r\\nContent-Length:{0}\\r\\n", bodyBytes.Length);
                headerBytes \= Encoding.UTF8.GetBytes(header);
            }
            socketClient.Send(statusBytes);
            socketClient.Send(headerBytes);
            socketClient.Send(new byte\[\] { (byte)'\\r', (byte)'\\n' });
            socketClient.Send(bodyBytes);

            socketClient.Close();
        }
    }
}

View Code

而後關閉記事本,將之.txt的後綴改成.cs的後綴(後綴是用來標示這個文件是什麼類型的文件,並不影響文件的內容)。

上述代碼至關於Web中的http.sys僞實現,是創建了通訊的socket服務端,並經過while循環來不斷的監視獲取包的數據實現最基本的監聽功能,最終咱們將經過csc.exe將該文本文件編譯成一個控制檯程序。

我已經在前面講過BCL,基礎類庫。在這部分代碼中,爲了完成我想要的功能,我用到了微軟已經幫咱們實現好了的String數據類型系列類(.NET下的一些數據類型)、Environment類(提供有關當前環境和平臺的信息以及操做它們的方法)、Console類(用於控制檯輸入輸出等)、Socket系列類(對tcp協議抽象的接口)、File文件系列類(對文件目錄等操做系統資源的一些操做)、Encoding類(字符流的編碼)等
這些類,都屬於BCL中的一部分,它們存在但不限於mscorlib.dll、System.dll、System.core.dll、System.Data.dll等這些程序集中。
附:不要糾結BCL到底存在於哪些dll中,總之,它是個物理分散,邏輯上的類庫總稱。

mscorlib.dll和System.dll的區別:https://stackoverflow.com/questions/402582/mscorlib-dll-system-dll

由於我用了這些類,那麼按照編程規則我必須在代碼中using這些類的命名空間,並經過csc.exe中的 /r:dll路徑 命令來爲生成的程序集註冊元數據表(即以AssemblyRef爲表明的程序集引用表)。
而這些代碼引用了4個命名空間,但實際上它們只被包含在mscorlib.dll和System.dll中,那麼我只須要在編譯的時候註冊這兩個dll的信息就好了。

好,接下來我將經過cmd運行csc.exe編譯器,再輸入編譯命令: csc /out:D:\demo.exe D:\dic\demo.cs /r:D:\dic\System.dll

/r:是將引用dll中的類型數據註冊到程序集中的元數據表中 。
/out:是輸出文件的意思,若是沒有該命令則默認輸出{name}.exe。
使用csc.exe編譯生成: https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/compiler-options/command-line-building-with-csc-exe
csc編譯命令行介紹:https://www.cnblogs.com/shuang121/archive/2012/12/24/2830874.html

總之,你除了要掌握基本的編譯指令外,當你打上這行命令並按回車後,必須知足幾個條件,1.是.cs後綴的c#格式文件,2.是 代碼語法等檢測分析必須正確,3.是 使用的類庫必須有出處(引用的dll),固然 由於我是編譯爲控制檯程序,因此還必須得有個靜態Main方法入口,以上缺一不可。

能夠看出,這段命令我是將 位於D:\dic\的demo.cs文件給編譯成 位於D:\名爲demo.exe的控制檯文件,而且由於在代碼中使用到了System.dll,因此還須要經過/r註冊該元數據表。
這裏得注意爲何沒有/r:mscorlib.dll,由於mscorlib.dll地位的特殊,因此csc老是對每一個程序集進行mscorlib.dll的註冊(自包含引用該dll),所以咱們能夠不用/r:mscorlib.dll這個引用命令,但爲了演示效果我仍是決定經過/nostdlib命令來禁止csc默認導入mscorlib.dll文件。

因此,最終命令是這樣的: csc D:\dic\demo.cs /r:D:\dic\mscorlib.dll /r:D:\dic\System.dll /nostdlib

由於沒有指定輸出文件/out選項, 因此會默認輸出在與csc同一目錄下名爲demo.exe的文件。事實上,在csc的命令中,若是你沒有指定路徑,那麼就默認採用在csc.exe的所在目錄的相對路徑。

而咱們能夠看到,在該目錄下有許多程序集,其中就包含咱們須要的System.dll和mscorlib.dll,因此咱們徹底能夠直接/r:mscorlib.dll /r:System.dll

而相似於System.dll、System.Data.dll這樣使用很是頻繁的程序集,咱們其實不用每次編譯的時候都去手動/r一下,對於須要重複勞動的編譯指令,咱們能夠將其放在後綴爲.rsp的指令文件中,而後在編譯時直接調用文件便可執行裏面的命令 @ {name}.rsp。

csc.exe默認包含csc.rsp文件,咱們能夠用/noconfig來禁止默認包含,而csc.rsp裏面已經寫好了咱們會常常用到的指令。
因此,最終我能夠這樣寫 csc D:\dic\demo.cs 直接生成控制檯應用程序。

.NET程序執行原理

好的,如今咱們已經有了一個demo.exe的可執行程序,它是如何被咱們運行的?。

C#源碼被編譯成程序集,程序集內主要是由一些元數據表和IL代碼構成,咱們雙擊執行該exe,Windows加載器將該exe(PE格式文件)給映射到虛擬內存中,程序集的相關信息都會被加載至內存中,並查看PE文件的入口點(EntryPoint)並跳轉至指定的mscoree.dll中的_CorExeMain函數,該函數會執行一系列相關dll來構造CLR環境,當CLR預熱後調用該程序集的入口方法Main(),接下來由CLR來執行託管代碼(IL代碼)。

JIT編譯

前面說了,計算機最終只識別二進制的機器碼,在CLR下有一個用來將IL代碼轉換成機器碼的引擎,稱爲Just In Time Compiler,簡稱JIT,CLR老是先將IL代碼按需經過該引擎編譯成機器指令再讓CPU執行,在這期間CLR會驗證代碼和元數據是否類型安全(在對象上只調用正肯定義的操做、標識與聲稱的要求一致、對類型的引用嚴格符合所引用的類型),被編譯過的代碼無需JIT再次編譯,而被編譯好的機器指令是被存在內存當中,當程序關閉後再打開仍要從新JIT編譯。

AOT編譯

CLR的內嵌編譯器是即時性的,這樣的一個很明顯的好處就是能夠根據當時本機狀況生成更有利於本機的優化代碼,但一樣的,每次在對代碼編譯時都須要一個預熱的操做,它須要一個運行時環境來支持,這之間仍是有消耗的。

而與即時編譯所對應的,就是提早編譯了,英文爲Ahead of Time Compilation,簡稱AOT,也稱之爲靜態編譯。
在.NET中,使用Ngen.exe或者開源的.NET Native能夠提早將代碼編譯成本機指令。

Ngen是將IL代碼提早給所有編譯成本機代碼並安裝在本機的本機映像緩存中,故而能夠減小程序因JIT預熱的時間,但一樣的也會有不少注意事項,好比因JIT的喪失而帶來的一些特性就沒有了,如類型驗證。Ngen僅是儘量代碼提早編譯,程序的運行仍須要完整的CLR來支持。

.NET Native在將IL轉換爲本機代碼的時候,會嘗試消除全部元數據將依靠反射和元數據的代碼替換爲靜態本機代碼,而且將完整的CLR替換爲主要包含垃圾回收器的重構運行時mrt100_app.dll。

.NET Native: https://docs.microsoft.com/zh-cn/dotnet/framework/net-native/
Ngen.exe:https://docs.microsoft.com/zh-cn/dotnet/framework/tools/ngen-exe-native-image-generator
Ngen與.NET Native比較:https://www.zhihu.com/question/27997478/answer/38978762

---------------------------------------------------

如今,咱們能夠經過ILDASM工具(一款查看程序集IL代碼的軟件,在Microsoft SDKs目錄中的子目錄中)來查看該程序集的元數據表和Main方法中間碼。

c#源碼第一行代碼:string rootDirectory = Environment.CurrentDirectory;被翻譯成IL代碼: call string [mscorlib/*23000001*/]System.Environment/*01000004*/::get_CurrentDirectory() /* 0A000003 */

這句話意思是調用 System.Environment類的get_CurrentDirectory()方法(屬性會被編譯爲一個私有字段+對應get/set方法)。

點擊視圖=>元信息=>顯示,便可查看該程序集的元數據。
咱們能夠看到System.Environment標記值爲01000004,在TypeRef類型引用表中找到該項:

注意圖,TypeRefName下面有該類型中被引用的成員,其標記值爲0A000003,也就是get_CurrentDirectory了。
而從其ResolutionScope指向位於0x23000001而得之,該類型存在於mscorlib程序集。

因而咱們打開mscorlib.dll的元數據清單,能夠在類型定義表(TypeDef)找到System.Environment,能夠從元數據得知該類型的一些標誌(Flags,常見的public、sealed、class、abstract),也得知繼承(Extends)於System.Object。在該類型定義下還有類型的相關信息,咱們能夠在其中找到get_CurrentDirectory方法。 咱們能夠獲得該方法的相關信息,這其中代表了該方法位於0x0002b784這個相對虛地址(RVA),接着JIT在新地址處理CIL,周而復始。

元數據在運行時的做用: https://docs.microsoft.com/zh-cn/dotnet/standard/metadata-and-self-describing-components#run-time-use-of-metadata

程序集的規則

上文我經過ILDASM來描述CLR執行代碼的方式,但還不夠具體,還須要補充的是對於程序集的搜索方式。

對於System.Environment類型,它存在於mscorlib.dll程序集中,demo.exe是個獨立的個體,它經過csc編譯的時候只是註冊了引用mscorlib.dll中的類型的引用信息,並無記錄mscorlib.dll在磁盤上的位置,那麼,CLR怎麼知道get_CurrentDirectory的代碼?它是從何處讀取mscorlib.dll的?
對於這個問題,.NET有個專門的概念定義,咱們稱爲 程序集的加載方式。

程序集的加載方式

對於自身程序集內定義的類型,咱們能夠直接從自身程序集中的元數據中獲取,對於在其它程序集中定義的類型,CLR會經過一組規則來在磁盤中找到該程序集並加載在內存。

CLR在查找引用的程序集的位置時候,第一個判斷條件是 判斷該程序集是否被簽名。
什麼是簽名?

強名稱程序集

就好比你們都叫張三,姓名都同樣,喊一聲張三不知道到底在叫誰。這時候咱們就必須擴展一下這個名字以讓它具備惟一性。

咱們能夠經過sn.exe或VS對項目右鍵屬性在簽名選項卡中採起RSA算法對程序集進行數字簽名(加密:公鑰加密,私鑰解密。簽名:私鑰簽名,公鑰驗證簽名),會將構成程序集的全部文件經過哈希算法生成哈希值,而後經過非對稱加密算法用私鑰簽名,最後公佈公鑰生成一串token,最終將生成一個由程序集名稱、版本號、語言文化、公鑰組成的惟一標識,它至關於一個強化的名稱,即強名稱程序集。
mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089

咱們平常在VS中的項目默認都沒有被簽名,因此就是弱名稱程序集。強名稱程序集是具備惟一標識性的程序集,而且能夠經過對比哈希值來比較程序集是否被篡改,不過仍然有不少手段和軟件能夠去掉程序集的簽名。

須要值得注意的一點是:當你試圖在已生成好的強名稱程序集中引用弱名稱程序集,那麼你必須對弱名稱程序集進行簽名並在強名稱程序集中從新註冊。
之因此這樣是由於一個程序集是否被篡改還要考慮到該程序集所引用的那些程序集,根據CLR搜索程序集的規則(下文會介紹),沒有被簽名的程序集能夠被隨意替換,因此考慮到安全性,強名稱程序集必須引用強名稱程序集,不然就會報錯:須要強名稱程序集。

.NET Framework 4.5中對強簽名的更改:https://docs.microsoft.com/zh-cn/dotnet/framework/app-domains/enhanced-strong-naming

程序集搜索規則

事實上,按照存儲位置來講,程序集分爲共享(全局)程序集和私有程序集。

CLR查找程序集的時候,會先判斷該程序集是否被強簽名,若是強簽名了那麼就會去共享程序集的存儲位置(後文的GAC)去找,若是沒找到或者該程序集沒有被強簽名,那麼就從該程序集的同一目錄下去尋找。

強名稱程序集是先找到與程序集名稱(VS中對項目右鍵屬性應用程序->程序集名稱)相等的文件名稱,而後 按照惟一標識再來確認,確認後CLR加載程序集,同時會經過公鑰效驗該簽名來驗證程序集是否被篡改(若是想跳過驗證可查閱https://docs.microsoft.com/zh-cn/dotnet/framework/app-domains/how-to-disable-the-strong-name-bypass-feature),若是強名稱程序集被篡改則報錯。

而弱名稱程序集則直接按照與程序集名稱相等的文件名稱來找,若是仍是沒有找到就以該程序集名稱爲目錄的文件夾下去找。總之,若是最終結果就是沒找到那就會報System.IO.FileNotFoundException異常,即嘗試訪問磁盤上不存在的文件失敗時引起的異常。

注意:此處文件名稱和程序集名稱是兩個概念,不要模棱兩可,文件CLR頭內嵌程序集名稱。

舉個例子:
我有一個控制檯程序,其路徑爲D:\Demo\Debug\demo.exe,經過該程序的元數據得知,其引用了一個程序集名稱爲aa的普通程序集,引用了一個名爲bb的強名稱程序集,該bb.dll的強名稱標識爲:xx001。
如今CLR開始搜索程序集aa,首先它會從demo.exe控制檯的同一目錄(也就是D:\Demo\Debug\)中查找程序集aa,搜索文件名爲aa.dll的文件,若是沒找到就在該目錄下以程序集名稱爲目錄的目錄中查找,也就是會查 D:\Demo\Debug\aa\aa.dll,這也找不到那就報錯。
而後CLR開始搜索程序集bb,CLR從demo.exe的元數據中發現bb是強名稱程序集,其標識爲:xx001。因而CLR會先從一個被定義爲GAC的目錄中去經過標識找,沒找到的話剩下的尋找步驟就和尋找aa同樣徹底一致了。

固然,你也能夠經過配置文件config中(配置文件存在於應用程序的同一目錄中)人爲增長程序集搜索規則:
1.在運行時runtime節點中,添加privatePath屬性來添加搜索目錄,不過只能填寫相對路徑:


<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
//程序集當前目錄下的相對路徑目錄,用;號分割

2.若是程序集是強簽名後的,那麼能夠經過codeBase來指定網絡路徑或本地絕對路徑。

複製代碼


<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">

<assemblyIdentity name="myAssembly" publicKeyToken="32ab4ba45e0a69a1" culture="neutral" />
<codeBase version="2.0.0.0" href="http://www.litwareinc.com/myAssembly.dll" />


複製代碼

固然,咱們還能夠在代碼中經過AppDomain類中的幾個成員來改變搜索規則,如AssemblyResolve事件、AppDomainSetup類等。

有關運行時節點的描述:https://docs.microsoft.com/zh-cn/dotnet/framework/configure-apps/file-schema/runtime/runtime-element

項目的依賴順序

若是沒有經過config或者在代碼中來設定CLR搜索程序集的規則,那麼CLR就按照默認的也就是我上述所說的模式來尋找。
因此若是咱們經過csc.exe來編譯項目,引用了其它程序集的話,一般須要將那些程序集複製到同一目錄下。故而每當咱們經過VS編譯器對項目右鍵從新生成項目(從新編譯)時,VS都會將引用的程序集給複製一份到項目bin\輸出目錄Debug文件夾下,咱們能夠經過VS中對引用的程序集右鍵屬性-複製本地 True/Flase 來決定這一默認行爲。

值得一提的是,項目間的生成是有序生成的,它取決於項目間的依賴順序。
好比Web項目引用BLL項目,BLL項目引用了DAL項目。那麼當我生成Web項目的時候,由於我要註冊Bll程序集,因此我要先生成Bll程序集,而BLL程序集又引用了Dal,因此又要先生成Dal程序集,因此程序集生成順序就是Dal=>BLL=>Web,項目越多編譯的時間就越久。

程序集之間的依賴順序決定了編譯順序,因此在設計項目間的分層劃分時不只要體現出層級職責,還要考慮到依賴順序。代碼存放在哪一個項目要有講究,不容許出現互相引用的狀況,好比A項目中的代碼引用B,B項目中的代碼又引用A。

爲何Newtonsoft.Json版本不一致?

而除了注意編譯順序外,咱們還要注意程序集間的版本問題,版本間的錯亂會致使程序的異常。

舉個經典的例子:Newtonsoft.Json的版本警告,大多數人都知道經過版本重定向來解決這個問題,但不多有人會琢磨爲何會出現這個問題,找了一圈文章,沒找到一個解釋的。

好比:
A程序集引用了 C盤:\Newtonsoft.Json 6.0程序集
B程序集引用了 從Nuget下載下來的Newtonsoft.Json 10.0程序集
此時A引用B,就會報:發現同一依賴程序集的不一樣版本間存在沒法解決的衝突 這一警告。

複製代碼

A:引用Newtonsoft.Json 6.0 Func()
{ var obj= Newtonsoft.Json.Obj;
B.JsonObj();
}

B: 引用Newtonsoft.Json 10.0 JsonObj()
{ return Newtonsoft.Json.Obj;
}

複製代碼

A程序集中的Func方法調用了B程序集中的JsonObj方法,JsonObj方法又調用了Newtonsoft.Json 10.0程序集中的對象,那麼當執行Func方法時程序就會異常,報System.IO.FileNotFoundException: 未能加載文件或程序集Newtonsoft.Json 10.0的錯誤。

這是爲何?
1.這是由於依賴順序引發的。A引用了B,首先會先生成B,而B引用了 Newtonsoft.Json 10.0,那麼VS就會將源引用文件(Newtonsoft.Json 10.0)複製到B程序集同一目錄(bin/Debug)下,名爲Newtonsoft.Json.dll文件,其內嵌程序集版本爲10.0。
2.而後A引用了B,因此會將B程序集和B程序集的依賴項(Newtonsoft.Json.dll)給複製到A的程序集目錄下,而A又引用了C盤的Newtonsoft.Json 6.0程序集文件,因此又將C:\Newtonsoft.Json.dll文件給複製到本身程序集目錄下。由於兩個Newtonsoft.Json.dll重名,因此直接覆蓋了前者,那麼只保留了Newtonsoft.Json 6.0。
3.當咱們調用Func方法中的B.Convert()時候,CLR會搜索B程序集,找到後再調用 return Newtonsoft.Json.Obj 這行代碼,而這行代碼又用到了Newtonsoft.Json程序集,接下來CLR搜索Newtonsoft.Json.dll,文件名稱知足,接下來CLR判斷其標識,發現版本號是6.0,與B程序集清單裏註冊的10.0版本不符,故而纔會報出異常:未能加載文件或程序集Newtonsoft.Json 10.0。

以上就是爲什麼Newtonsoft.Json版本不一致會致使錯誤的緣由,其也詮釋了CLR搜索程序集的一個過程。
那麼,若是我執意如此,有什麼好的解決方法能讓程序順利執行呢?有,有2個方法。

第一種:經過bindingRedirect節點重定向,即當找到10.0的版本時,給定向到6.0版本


<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">

<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="10.0.0.0" newVersion="6.0.0.0" />


View Code

如何在編譯時加載兩個相同的程序集?

注意:我看過有的文章裏寫的一個AppDomain只能加載一個相同的程序集,不少人都覺得不能同時加載2個不一樣版本的程序集,實際上CLR是能夠同時加載Newtonsoft.Json 6.0和Newtonsoft.Json 10.0的。

第二種:對每一個版本指定codeBase路徑,而後分別放上不一樣版本的程序集,這樣就能夠加載兩個相同的程序集。


<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">

<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<codeBase version="6.0.0.0" href="D:\6.0\Newtonsoft.Json.dll" />


<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<codeBase version="10.0.0.0" href="D:\10.0\Newtonsoft.Json.dll" />


View Code

如何同時調用兩個兩個相同命名空間和類型的程序集?

除了程序集版本不一樣外,還有一種狀況就是,我一個項目同時引用了程序集A和程序集B,但程序集A和程序集B中的命名空間和類型名稱徹底如出一轍,這個時候我調用任意一個類型都沒法區分它是來自於哪一個程序集的,那麼這種狀況咱們能夠使用extern alias外部別名。
咱們須要在全部代碼前定義別名,extern alias a;extern alias b;,而後在VS中對引用的程序集右鍵屬性-別名,分別將其更改成a和b(或在csc中經過/r:{別名}={程序集}.dll)。
在代碼中經過 {別名}::{命名空間}.{類型}的方式來使用。
extern-alias介紹: https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/keywords/extern-alias

共享程序集GAC

我上面說了這麼多有關CLR加載程序集的細節和規則,事實上,相似於mscorlib.dll、System.dll這樣的FCL類庫被引用的如此頻繁,它已是咱們.NET編程中必不可少的一部分,幾盡每一個項目都會引用,爲了避免再每次使用的時候都複製一份,因此計算機上有一個位置專門存儲這些咱們都會用到的程序集,叫作全局程序集緩存(Global Assembly Cache,GAC),這個位置通常位於C:\Windows\Microsoft.NET\assembly和3.5以前版本的C:\Windows\assembly。
既然是共享存放的位置,那不可避免的會遇到文件名重複的狀況,那麼爲了杜絕該類狀況,規定在GAC中只能存在強名稱程序集,每當CLR要加載強名稱程序集時,會先經過標識去GAC中查找,而考慮到程序集文件名稱一致但版本文化等複雜的狀況,因此GAC有本身的一套目錄結構。咱們若是想將本身的程序集放入GAC中,那麼就必須先簽名,而後經過如gacutil.exe工具(其存在於命令行工具中 https://docs.microsoft.com/zh-cn/dotnet/framework/tools/developer-command-prompt-for-vs中)來註冊至GAC中,值得一提的是在將強名稱程序集安裝在GAC中,會效驗簽名。

GAC工具: https://docs.microsoft.com/en-us/dotnet/framework/tools/gacutil-exe-gac-tool

延伸

CLR是按需加載程序集的,沒有執行代碼也就沒有調用相應的指令,沒有相應的指令,CLR也不會對其進行相應的操做。 當咱們執行Environment.CurrentDirectory這段代碼的時候,CLR首先要獲取Environment類型信息,經過自身元數據得知其存在mscorlib.dll程序集中,因此CLR要加載該程序集,而mscorlib.dll又因爲其地位特殊,早在CLR初始化的時候就已經被類型加載器自動加載至內存中,因此這行代碼能夠直接在內存中讀取到類型的方法信息。
在這個章節,我雖然描述了CLR搜索程序集的規則,但事實上,加載程序集讀取類型信息遠遠沒有這麼簡單,這涉及到了屬於.NET Framework獨有的"應用程序域"概念和內存信息的查找。

簡單延伸兩個問題,mscorlib.dll被加載在哪裏?內存堆中又是什麼樣的一個狀況?

應用程序域

傳統非託管程序是直接承載在Windows進程中,託管程序是承載在.NET虛擬機CLR上的,而在CLR中管控的這部分資源中,被分紅了一個個邏輯上的分區,這個邏輯分區被稱爲應用程序域,是.NET Framework中定義的一個概念。
由於堆內存的構建和刪除都經過GC去託管,下降了人爲出錯的概率,在此特性基礎上.NET強調在一個進程中經過CLR強大的管理創建起對資源邏輯上的隔離區域,每一個區域的應用程序互不影響,從而讓託管代碼程序的安全性和健壯性獲得了提高。

熟悉程序集加載規則和AppDomain是在.NET技術下進行插件編程的前提。AppDomain這部分概念並不複雜。
當啓動一個託管程序時,最早啓動的是CLR,在這過程當中會經過代碼初始化三個邏輯區域,最早是SystemDomain系統程序域,而後是SharedDoamin共享域,最後是{程序集名稱}Domain默認域。

系統程序域裏維持着一些系統構建項,咱們能夠經過這些項來監控並管理其它應用程序域等。共享域存放着其它域都會訪問到的一些信息,當共享域初始化完畢後,會自動加載mscorlib.dll程序集至該共享域。而默認域則用儲存自身程序集的信息,咱們的主程序集就會被加載至這個默認域中,執行程序入口方法,在沒有特殊動做外所產生的一切耗費都發生在該域。

咱們能夠在代碼中建立和卸載應用程序域,域與域之間有隔離性,掛掉A域不會影響到B域,而且對於每個加載的程序集都要指定域的,沒有在代碼中指定域的話,默認都是加載至默認域中。
AppDomain能夠想象成組的概念,AppDomain包含了咱們加載的一組程序集。咱們經過代碼卸載AppDomain,即同時卸載了該AppDomain中所加載的全部程序集在內存中的相關區域。

AppDomain的初衷是邊緣隔離,它可讓程序不從新啓動而長時間運行,圍繞着該概念創建的體系從而讓咱們可以使用.NET技術進行插件編程。

當咱們想讓程序在不關閉不從新部署的狀況下添加一個新的功能或者改變某一塊功能,咱們能夠這樣作:將程序的主模塊仍默認加載至默認域,再建立一個新的應用程序域,而後將須要更改或替換的模塊的程序集加載至該域,每當更改和替換的時候直接卸載該域便可。 而由於域的隔離性,我在A域和B域加載同一個程序集,那麼A域和B域就會各存在內存地址不一樣但數據相同的程序集數據。

跨邊界訪問

事實上,在開發中咱們還應該注意跨域訪問對象的操做(即在A域中的程序集代碼直接調用B域中的對象)是與日常編程中有所不一樣的,一個域中的應用程序不能直接訪問另外一個域中的代碼和數據,對於這樣的在進程內跨域訪問操做分兩類。

一是按引用封送,須要繼承System.MarshalByRefObject,傳遞的是該對象的代理引用,與源域有相同的生命週期。
二是按值封送,須要被[Serializable]標記,是經過序列化傳遞的副本,副本與源域的對象無關。
不管哪一種方式都涉及到兩個域直接的封送、解封,因此跨域訪問調用不適用於太高頻率。
(好比,原來你是這樣調用對象: var user=new User(); 如今你要這樣:var user=(User){應用程序域對象實例}.CreateInstanceFromAndUnwrap("Model.dll","Model.User"); )

值得注意的是,應用程序域是對程序集的組的劃分,它與進程中的線程是兩個一橫一豎,方向不同的概念,不該該將這2個概念放在一塊兒比較。咱們能夠經過Thread.GetDomain來查看執行線程所在的域。
應用程序域在類庫中是System.AppDomain類,部分重要的成員有:

複製代碼

獲取當前 System.Threading.Thread 的當前應用程序域 public static AppDomain CurrentDomain { get; }
使用指定的名稱新建應用程序域 public static AppDomain CreateDomain(string friendlyName);
卸載指定的應用程序域。 public static void Unload(AppDomain domain);
指示是否對當前進程啓用應用程序域的 CPU 和內存監視,開啓後能夠根據相關屬性進行監控 public static bool MonitoringIsEnabled { get; set; }
當前域託管代碼拋出異常時最早發生的一個事件,框架設計中能夠用到 public event EventHandler FirstChanceException;
當某個異常未被捕獲時調用該事件,如代碼裏只catch了a異常,實際產生的是 b異常,那麼b異常就沒有捕捉到。 public event UnhandledExceptionEventHandler UnhandledException;
爲指定的應用程序域屬性分配指定值。該應用程序域的局部存儲值,該存儲不劃分上下文和線程,都可經過GetData獲取。 public void SetData(string name, object data);
若是想使用託管代碼來覆蓋CLR的默認行爲https://msdn.microsoft.com/zh-cn/library/system.appdomainmanager(v=vs.85).aspx
public AppDomainManager DomainManager { get; }
返回域的配置信息,如在config中配置的節點信息 public AppDomainSetup SetupInformation { get; }

複製代碼

應用程序域: https://docs.microsoft.com/zh-cn/dotnet/framework/app-domains/application-domains

AppDomain和AppPool

注意:此處的AppDomain應用程序域 和 IIS中的AppPool應用程序池 是2個概念,AppPool是IIS獨有的概念,它也至關於一個組的概念,對網站進行劃組,而後對組進行一些如進程模型、CPU、內存、請求隊列的高級配置。

內存

應用程序域把資源給隔離開,這個資源,主要指內存。那麼什麼是內存呢?

要知道,程序運行的過程就是電腦不斷經過CPU進行計算的過程,這個過程須要讀取併產生運算的數據,爲此咱們須要一個擁有足夠容量可以快速與CPU交互的存儲容器,這就是內存了。對於內存大小,32位處理器,尋址空間最大爲2的32次方byte,也就是4G內存,除去操做系統所佔用的公有部分,進程大概能佔用2G內存,而若是是64位處理器,則是8T。

而在.NET中,內存區域分爲堆棧和託管堆。

堆棧和堆的區別

堆和堆棧就內存而言只不過是地址範圍的區別。不過堆棧的數據結構和其存儲定義讓其在時間和空間上都緊密的存儲,這樣能帶來更高的內存密度,能在CPU緩存和分頁系統表現的更好。故而訪問堆棧的速度整體來講比訪問堆要快點。

線程堆棧

操做系統會爲每條線程分配必定的空間,Windwos爲1M,這稱之爲線程堆棧。在CLR中的棧主要用來執行線程方法時,保存臨時的局部變量和函數所需的參數及返回的值等,在棧上的成員不受GC管理器的控制,它們由操做系統負責分配,當線程走出方法後,該棧上成員採用後進先出的順序由操做系統負責釋放,執行效率高。
而託管堆則沒有固定容量限制,它取決於操做系統容許進程分配的內存大小和程序自己對內存的使用狀況,託管堆主要用來存放對象實例,不須要咱們人工去分配和釋放,其由GC管理器託管。

爲何值類型存儲在棧上

不一樣的類型擁有不一樣的編譯時規則和運行時內存分配行爲,咱們應知道,C# 是一種強類型語言,每一個變量和常量都有一個類型,在.NET中,每種類型又被定義爲值類型或引用類型。

使用 struct、enum 關鍵字直接派生於System.ValueType定義的類型是值類型,使用 class、interface、delagate 關鍵字派生於System.Object定義的類型是引用類型。
對於在一個方法中產生的值類型成員,將其值分配在棧中。這樣作的緣由是由於值類型的值其佔用固定內存的大小。

C#中int關鍵字對應BCL中的Int32,short對應Int16。Int32爲2的32位,若是把32個二進制數排列開來,咱們要求既能表達正數也能表達負數,因此得須要其中1位來表達正負,首位是0則爲+,首位是1則爲-,那麼咱們能表示數據的數就只有31位了,而0是介於-1和1之間的整數,因此對應的Int32能表現的就是2的31次方到2的31次方-1,即2147483647和-2147483648這個整數段。

1個字節=8位,32位就是4個字節,像這種以Int32爲表明的值類型,自己就是固定的內存佔用大小,因此將值類型放在內存連續分配的棧中。

託管堆模型

而引用類型相比值類型就有點特殊,newobj建立一個引用類型,因其類型內的引用對象能夠指向任何類型,故而沒法準確得知其固定大小,因此像對於引用類型這種沒法預知的容易產生內存碎片的動態內存,咱們把它放到託管堆中存儲。

託管堆由GC託管,其分配的核心在於堆中維護着一個nextObjPtr指針,咱們每次實例(new)一個對象的時候,CLR將對象存入堆中,並在棧中存放該對象的起始地址,而後該指針都會根據該對象的大小來計算下一個對象的起始地址。不一樣於值類型直接在棧中存放值,引用類型則還須要在棧中存放一個表明(指向)堆中對象的值(地址)。

而託管堆又能夠因存儲規則的不一樣將其分類,託管堆能夠被分爲3類:

  • 1.用於託管對象實例化的垃圾回收堆,又以存儲對象大小分爲小對象(<85000byte)的GC堆(SOH,Small Object Heap)和用於存儲大對象實例的(>=85000byte)大對象堆(LOG,Larage Object Heap)。
  • 2.用於存儲CLR組件和類型系統的加載(Loader)堆,其中又以使用頻率分爲常常訪問的高頻堆(裏面包含有MethodTables方法表, MeghodDescs方法描述, FieldDescs方法描述和InterfaceMaps接口圖),和較低的低頻堆,和Stub堆(輔助代碼,如JIT編譯後修改機器代碼指令地址環節)。
  • 3.用於存儲JIT代碼的堆及其它雜項的堆。

加載程序集就是將程序集中的信息給映射在加載堆,對產生的實例對象存放至垃圾回收堆。前文說過應用程序域是指經過CLR管理而創建起的邏輯上的內存邊界,那麼每一個域都有其本身的加載堆,只有卸載應用程序域的時候,纔會回收該域對應的加載堆。

而加載堆中的高頻堆包含的有一個很是重要的數據結構表---方法表,每一個類型都僅有一份方法表(MethodTables),它是對象的第一個實例建立前的類加載活動的結果,它主要包含了咱們所關注的3部分信息:

  • 1包含指向EEClass的一個指針。EEClass是一個很是重要的數據結構,當類加載器加載到該類型時會從元數據中建立出EEClass,EEClass裏主要存放着與類型相關的表達信息。
  • 2包含指向各自方法的方法描述器(MethodDesc)的指針邏輯組成的線性表信息:繼承的虛函數, 新虛函數, 實例方法, 靜態方法。
  • 3包含指向靜態字段的指針。

那麼,實例一個對象,CLR是如何將該對象所對應的類型行爲及信息的內存位置(加載堆)關聯起來的呢?

原來,在託管堆上的每一個對象都有2個額外的供於CLR使用的成員,咱們是訪問不到的,其中一個就是類型對象指針,它指向位於加載堆中的方法表從而讓類型的狀態和行爲關聯了起來, 類型指針的這部分概念咱們能夠想象成obj.GetType()方法得到的運行時對象類型的實例。而另外一個成員就是同步塊索引,其主要用於2點:1.關聯內置SyncBlock數組的項從而完成互斥鎖等目的。 2.是對象Hash值計算的輸入參數之一。

上述gif是我簡單畫的一個圖,能夠看到對於方法中申明的值類型變量,其在棧中做爲一塊值表示,咱們能夠直接經過c#運算符sizeof來得到值類型所佔byte大小。而方法中申明的引用類型變量,其在託管堆中存放着對象實例(對象實例至少會包含上述兩個固定成員以及實例數據,可能),在棧中存放着指向該實例的地址。

當我new一個引用對象的時候,會先分配同步塊索引(也叫對象頭字節),而後是類型指針,最後是類型實例數據(靜態字段的指針存在於方法表中)。會先分配對象的字段成員,而後分配對象父類的字段成員,接着再執行父類的構造函數,最後纔是本對象的構造函數。這個多態的過程,對於CLR來講就是一系列指令的集合,因此不能糾結new一個子類對象是否會也會new一個父類對象這樣的問題。而也正是由於引用類型的這樣一個特徵,咱們雖然能夠估計一個實例大概佔用多少內存,但對於具體佔用的大小,咱們須要專門的工具來測量。

對於引用類型,u2=u1,咱們在賦值的時候,實際上賦的是地址,那麼我改動數據其實是改動該地址指向的數據,這樣一來,由於u2和u1都指向同一塊區域,因此我對u1的改動會影響到u2,對u2的改動會影響到u1。若是我想互不影響,那麼我能夠繼承IClone接口來實現內存克隆,已有的CLR實現是淺克隆方法,但也只能克隆值類型和String(string是個特殊的引用類型,對於string的更改,其會產生一個新實例對象),若是對包含其它引用類型的這部分,咱們能夠本身經過其它手段實現深克隆,如序列化、反射等方式來完成。而若是引用類型中包含有值類型字段,那麼該字段仍然分配在堆上。

對於值類型,a=b,咱們在賦值的時候,其實是新建了個值,那麼我改動a的值那就只會改動a的值,改動b的值就只會改動b的值。而若是值類型(如struct)中包含的有引用類型,那麼還是一樣的規則,引用類型的那部分實例在託管堆中,地址在棧上。

我若是將值類型放到引用類型中(如:object a=3),會在棧中生成一個地址,在堆中生成該值類型的值對象,還會再生成這類型指針和同步塊索引兩個字段,這也就是常說裝箱,反過來就是拆箱。每一次的這樣的操做,都會涉及到內存的分佈、拷貝,可見,裝箱和拆箱是有性能損耗,所以應該減小值類型和引用類型之間轉換的次數。
但對於引用類型間的子類父類的轉換,僅是指令的執行消耗,幾盡沒有開銷。

選class仍是struct

那麼我究竟是該new一個class呢仍是選擇struct呢?

經過上文知道對於class,用完以後對象仍然存在託管堆,佔用內存。對於struct,用完以後直接由操做系統銷燬。那麼在實際開發中定義類型時,選擇class仍是struct就須要注意了,要綜合應用場景來辨別。struct存在於棧上,棧和託管堆比較,最大的優點就是即用即毀。因此若是咱們單純的傳遞一個類型,那麼選擇struct比較合適。但須注意線程堆棧有容量限制,不可多存放超大量的值類型對象,而且由於是值類型直接傳遞副本,因此struct做爲方法參數是線程安全的,但一樣要避免裝箱的操做。而相比較class,若是類型中還須要多一些封裝繼承多態的行爲,那麼class固然是更好的選擇。

GC管理器

值得注意的是,當我new完一個對象再也不使用的時候,這個對象在堆中所佔用的內存如何處理?
在非託管世界中,能夠經過代碼手動進行釋放,但在.NET中,堆徹底由CLR託管,也就是說GC堆是如何具體來釋放的呢?

當GC堆須要進行清理的時候,GC收集器就會經過必定的算法來清理堆中的對象,而且版本不一樣算法也不一樣。最主要的則爲Mark-Compact標記-壓縮算法。
這個算法的大概含義就是,經過一個圖的數據結構來收集對象的根,這個根就是引用地址,能夠理解爲指向託管堆的這根關係線。當觸發這個算法時,會檢查圖中的每一個根是否可達,若是可達就對其標記,而後在堆上找到剩餘沒有標記(也就是不可達)的對象進行刪除,這樣,那些不在使用的堆中對象就刪除了。

前面說了,由於nextObjPtr的緣故,在堆中分配的對象都是連續分配的,由於未被標記而被刪除,那麼通過刪除後的堆就會顯得支零破碎,那麼爲了不空間碎片化,因此須要一個操做來讓堆中的對象再變得緊湊、連續,而這樣一個操做就叫作:Compact壓縮。
而對堆中的分散的對象進行挪動後,還會修改這些被挪動對象的指向地址,從而得以正確的訪問,最後從新更新一下nextObjPtr指針,周而復始。

而爲了優化內存結構,減小在圖中搜索的成本,GC機制又爲每一個託管堆對象定義了一個屬性,將每一個對象分紅了3個等級,這個屬性就叫作:代,0代、1代、2代。

每當new一個對象的時候,該對象都會被定義爲第0代,當GC開始回收的時候,先從0代回收,在這一次回收動做以後,0代中沒有被回收的對象則會被定義成第1代。當回收第1代的時候,第1代中沒有被清理掉的對象就會被定義到第2代。
CLR初始化時會爲0/1/2這三代選擇一個預算的容量。0代一般以256 KB-4 MB之間的預算開始,1代的典型起始預算爲512 KB-4 MB,2代不受限制,最大可擴展至操做系統進程的整個內存空間。

好比第0代爲256K,第1代爲2MB。咱們不停的new對象,直到這些對象達到256k的時候,GC會進行一次垃圾回收,假設此次回收中回收了156k的不可達對象,剩餘100k的對象沒有被回收,那麼這100k的對象就被定義爲第1代。如今就變成了第0代裏面什麼都沒有,第1代裏放的有100k的對象。這樣周而復始,GC清除的永遠都只有第0代對象,除非當第一代中的對象累積達到了定義的2MB的時候,纔會連同清理第1代,而後第1代中活着的部分再升級成第二代...

第二代的容量是沒有限制,可是它有動態的閾值(由於等到整個內存空間已滿以執行垃圾回收是沒有意義的),當達到第二代的閾值後會觸發一次0/1/2代完整的垃圾收集。

也就是說,代數越長說明這個對象經歷了回收的次數也就越多,那麼也就意味着該對象是不容易被清除的。
這種分代的思想來將對象分割成新老對象,進而配對不一樣的清除條件,這種巧妙的思想避免了直接清理整個堆的尷尬。

弱引用、弱事件

GC收集器會在第0代飽和時開始回收託管堆對象,對於那些已經申明或綁定的不經訪問的對象或事件,由於不常常訪問並且還佔內存(有點懶加載的意思),因此即時對象可達,但我想在GC回收的時候仍然對其回收,當須要用到的時候再建立,這種狀況該怎麼辦?

那麼這其中就引入了兩個概念:
WeakReference弱引用、WeakEventManager弱事件
對於這2兩個不區分語言的共同概念,你們可自行擴展百度,此處就再也不舉例。

GC堆回收

那麼除了經過new對象而達到代的閾(臨界)值時,還有什麼可以致使垃圾堆進行垃圾回收呢? 還可能windows報告內存不足、CLR卸載AppDomain、CLR關閉等其它特殊狀況。

或者,咱們還能夠本身經過代碼調用。

.NET有GC來幫助開發人員管理內存,而且版本也在不斷迭代。GC幫咱們託管內存,但仍然提供了System.GC類讓開發人員可以輕微的協助管理。 這其中有一個能夠清理內存的方法(並無提供清理某個對象的方法):GC.Collect方法,能夠對全部或指定代進行即時垃圾回收(若是想調試,需在release模式下才有效果)。這個方法儘可能別用,由於它會擾亂代與代間的秩序,從而讓低代的垃圾對象跑到生命週期長的高代中。

GC還提供了,判斷當前對象所處代數、判斷指定代數經歷了多少次垃圾回收、獲取已在託管堆中分配的字節數這樣的三個方法,咱們能夠從這3個方法簡單的瞭解託管堆的狀況。

託管世界的內存不須要咱們打理,咱們沒法從代碼中得知具體的託管對象的大小,你若是想追求對內存最細微的控制,顯然C#並不適合你,不過相似於有關內存把控的這部分功能模塊,咱們能夠經過非託管語言來編寫,而後經過.NET平臺的P/Invoke或COM技術(微軟爲CLR定義了COM接口並在註冊表中註冊)來調用。

像FCL中的源碼,不少涉及到操做系統的諸如 文件句柄、網絡鏈接等外部extren的底層方法都是非託管語言編寫的,對於這些非託管模塊所佔用的資源,咱們能夠經過隱式調用析構函數(Finalize)或者顯式調用的Dispose方法經過在方法內部寫上非託管提供的釋放方法來進行釋放。

像文中示例的socket就將釋放資源的方法寫入Dispose中,析構函數和Close方法均調用Dispose方法以此完成釋放。事實上,在FCL中的使用了非託管資源的類大多都遵循IDispose模式。而若是你沒有釋放非託管資源直接退出程序,那麼操做系統會幫你釋放該程序所佔的內存的。

垃圾回收對性能的影響

還有一點,垃圾回收是對性能有影響的。
GC雖然有不少優化策略,但總之,只要當它開始回收垃圾的時候,爲了防止線程在CLR檢查期間對對象更改狀態,因此CLR會暫停進程中的幾乎全部線程(因此線程太多也會影響GC時間),而暫停的時間就是應用程序卡死的時間,爲此,對於具體的處理細節,GC提供了2種配置模式讓咱們選擇。

第一種爲:單CPU的工做站模式,專爲單CPU處理器定作。這種模式會採用一系列策略來儘量減小GC回收中的暫停時間。
而工做站模式又分爲併發(或後臺)與不併發兩種,併發模式表現爲響應時間快速,不併發模式表現爲高吞吐量。

第二種爲:多CPU的服務器模式,它會爲每一個CPU都運行一個GC回收線程,經過並行算法來使線程能真正同時工做,從而得到性能的提高。

咱們能夠經過在Config文件中更改配置來修改GC模式,若是沒有進行配置,那麼應用程序老是默認爲單CPU的工做站的併發模式,而且若是機器爲單CPU的話,那麼配置服務器模式則無效。

若是在工做站模式中想禁用併發模式,則應該在config中運行時節點添加
若是想更改至服務器模式,則能夠添加

複製代碼






複製代碼

gcConcurrent: https://docs.microsoft.com/zh-cn/dotnet/framework/configure-apps/file-schema/runtime/gcconcurrent-element
gcServer: https://docs.microsoft.com/zh-cn/dotnet/framework/configure-apps/file-schema/runtime/gcserver-element

性能建議

雖然咱們能夠選擇適合的GC工做模式來改善垃圾回收時的表現,但在實際開發中咱們更應該注意減小沒必要要的內存開銷。

幾個建議是,減換須要建立大量的臨時變量的模式、考慮對象池、大對象使用懶加載、對固定容量的集合指定長度、注意字符串操做、注意高頻率的隱式裝箱操做、延遲查詢、對於不須要面向對象特性的類用static、須要高性能操做的算法改用外部組件實現(p/invoke、com)、減小throw次數、注意匿名函數捕獲的外部對象將延長生命週期、能夠閱讀GC相關運行時配置在高併發場景注意變換GC模式...

對於.NET中改善性能可延伸閱讀 https://msdn.microsoft.com/zh-cn/library/ms973838.aspxhttps://msdn.microsoft.com/library/ms973839.aspx

.NET程序執行圖

至此,.NET Framework上的三個重要概念,程序集、應用程序域、內存在本文講的差很少了,我畫了一張圖簡單的概述.NET程序的一個執行流程:

對於後文,我將單獨的介紹一些其它雜項,首先是.NET平臺的安全性。

.NET的安全性

.NET Framework中的安全機制分爲 基於角色的安全機制 和 代碼訪問安全機制 。

基於角色的安全性

基於角色的安全機制做爲傳統的訪問控制,其運用的很是普遍,如操做系統的安全策略、數據庫的安全策略等等...它的概念就至關於咱們常常作的那些RBAC權限管理系統同樣,用戶關聯角色,角色關聯權限,權限對應着操做。
整個機制的安全邏輯就和咱們平時編寫代碼判斷是同樣的,大體能夠分爲兩個步驟.

第一步就是建立一個主體,而後標識這個主體是什麼身份(角色) ,第二步就是 身份驗證,也就是if判斷該身份是否能夠這樣操做。

而在.NET Framework中,這主體能夠是Windows帳戶,也能夠是自定義的標識,經過生成如當前線程或應用程序域使用的主體相關的信息來支持受權。
好比,構造一個表明當前登陸帳戶的主體對象WindowsPrincipal,而後經過 AppDomain.CurrentDomain.SetThreadPrincipal(主體對象);或Thread.CurrentPrincipal的set方法來設置應用程序域或線程的主體對象, 最後使用System.Security.Permissions.PrincipalPermission特性來標記在方法上來進行受權驗證。

如圖,我當前登陸帳號名稱爲DemoXiaoZeng,而後經過Thread.CurrentPrincipal設置當前主體,執行aa方法,順利打印111。若是檢測到PrincipalPermission類中的Name屬性值不是當前登陸帳號,那麼就報錯:對主體權限請求失敗。

在官方文檔中有對.NET Framework基於角色的安全性的詳細的介紹,感興趣能夠去了解 https://docs.microsoft.com/zh-cn/dotnet/standard/security/principal-and-identity-objects#principal-objects

代碼訪問安全性

在.NET Framework中還有一個安全策略,叫作 代碼訪問安全Code Access Security,也就是CAS了。

代碼訪問安全性在.NET Framework中是用來幫助限制代碼對受保護資源和操做的訪問權限。
舉個例子,我經過建立一個FileIOPermission對象來限制對後續代碼對D盤的文件和目錄的訪問,若是後續代碼對D盤進行資源操做則報錯。

FileIOPermission是代碼控制訪問文件和文件夾的能力。除了FileIOPermission外,還有如PrintingPermission代碼控制訪問打印機的權限、RegistryPermission代碼控制操做註冊表的權限、SocketPermission控制接受鏈接或啓動Socket鏈接的權限。

對於這些經過代碼來對受保護資源和操做的權限限制,也就是這些類名後綴爲Permission的類,它們叫作 Permissions(權限),都繼承自CodeAccessPermission,都有如Demand,Assert,Deny,PermitOnly,IsSubsetOf,Intersect和Union這些方法,在MSDN上有完整的權限列表:https://msdn.microsoft.com/en-us/library/h846e9b3(v=vs.100).aspx

爲了肯定代碼是否有權訪問某一資源或執行某一操做,CLR的安全系統將審覈調用堆棧,以將每一個調用方得到的權限與要求的權限進行比較。 若是調用堆棧中的任何調用方不具有要求的權限,則會引起安全性異常並拒絕訪問。

圖出自 https://docs.microsoft.com/zh-cn/dotnet/framework/misc/code-access-security

而除了Permissions權限,代碼訪問安全性機制還有 權限集、證據、代碼組、策略等概念。這些概念讓CAS如此強大,但相應的,它們也讓CAS變得複雜,必須爲每一個特定機器定義正確的PermissionSet和Code Groups才能設置成一個成功的CAS策略。

考慮到這層緣由,Microsoft .NET安全小組決定從頭開始重建代碼訪問安全性。在.NET Framework4.0以後,就再也不使用以前的那套CAS模型了,而是使用.NET Framework 2.0中引入的安全透明模型,而後稍加修改,修改後的安全透明模型成爲保護資源的標準方法,被稱之爲:安全透明度級別2

安全透明度2介紹:https://msdn.microsoft.com/en-us/library/dd233102(v=vs.100).aspx
.NET Framework4.0的安全更改:https://msdn.microsoft.com/en-us/library/dd233103(v=vs.100).aspx
一個完整的CAS演示:https://www.codeproject.com/Articles/5724/Understanding-NET-Code-Access-Security

對於安全透明度級別2我將再也不介紹,感興趣的能夠看我推薦的這2篇文章,對Level2的安全透明度介紹的比較詳細,包括實踐、遷移。
https://www.red-gate.com/simple-talk/dotnet/.net-framework/whats-new-in-code-access-security-in-.net-framework-4.0---part-i/
https://www.red-gate.com/simple-talk/dotnet/net-framework/whats-new-in-code-access-security-in-net-framework-4-0-part-2/

----------------------------------------------

須注意:
.NET平臺上的安全機制,僅僅是.NET平臺上的,所以它只限制於託管代碼,咱們能夠直接調用非託管代碼或進程通訊間接調用非託管代碼等多個手段來突破對託管代碼 操做資源的限制。

事實上,咱們在日常項目中代碼編寫的安全機制(業務邏輯身份驗證、項目框架驗證)與這些平臺級的安全機制沒什麼不一樣。咱們能夠理解爲代碼寫的位置不同,.NET安全機制是寫在CLR組件中,而咱們的安全機制是寫在上層的代碼中。這些平臺級的標識更多的是和操做系統用戶有關,而咱們項目代碼中的標識則是和在數據庫中註冊的用戶有關, 你們都是經過if else來去判斷,判斷的主體和格局不同,邏輯本質都是相同的。

NET Core不支持代碼訪問安全性和安全性透明性。

.NET是什麼

我在前文對.NET系統概述時,有的直接稱.NET,有的稱.NET Framework。那麼準確來講什麼是.NET?什麼又是.NET Framework呢?

.NET是一個微軟搭造的開發者平臺,它主要包括:

  • 1.支持(面向)該平臺的編程語言(如C#、Visual Basic、C++/CLI、F#、IronPython、IronRuby...),
  • 2.用於該平臺下開發人員的技術框架體系(.NET Framework、.NET Core、Mono、UWP等),

  • 1.定義了通用類型系統,龐大的CTS體系
  • 2.用於支撐.NET下的語言運行時的環境:CLR
  • 3..NET體系技術的框架庫FCL

  • 3.用於支持開發人員開發的軟件工具(即SDK,如VS201七、VS Code等)

.NET Framework是什麼

事實上,像我上面講的那些諸如程序集、GC、AppDomain這樣的爲CLR的一些概念組成,實質上指的是.NET Framework CLR。

.NET平臺是微軟爲了佔據開發市場而成立的,不是無利益驅動的純技術平臺的那種東西。基於該平臺下的技術框架也由於 商業間的利益 從而和微軟自身的Windows操做系統所綁定。因此雖然平臺雄心和口號很大,但不少框架類庫技術都是以Windows系統爲藍本,這樣就致使,雖然.NET各方面都挺好,可是用.NET就必須用微軟的東西,直接造成了技術-商業的綁定。

.NET Framework就是.NET 技術框架組成在Windows系統下的具體的實現,和Windows系統高度耦合,上文介紹的.NET系統,就是指.NET Framework。

部署.net Framework :https://docs.microsoft.com/zh-cn/dotnet/framework/deployment/deployment-guide-for-developers
.NET Framework高級開發:https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2008/29eafad8(v%3dvs.90)
.NET Framework源碼在線瀏覽:https://referencesource.microsoft.com/

如何在VS中調試.NET Framework源代碼

最爲關鍵的是pdb符號文件,沒得符號就調不了,對於符號咱們從微軟的符號服務器上下載(默認就已配置),還得有源代碼來調試。

點擊工具-選項-調試-常規,若是你以前沒有在該配置欄配置過,那麼你就勾選 啓用源服務器支持 、啓用.net Framework源代碼單步執行,而後將 要求源文件與原始版本徹底匹配 給取消掉。

而後就是下載pdb符號文件了,若是想直接下載那麼能夠在調試-符號這欄 將Microsoft符號服務器給勾上 。若是想按需下載,那麼在調試的時候,能夠點擊調試-窗口 選擇 模塊/調用堆棧 來選擇本身想加載的去加載。

而後至 https://referencesource.microsoft.com/網站 點擊右上角下載源代碼。當你調試代碼的時候,會提示你無可用源,這個時候你再將你下載下來的源碼文件給瀏覽查找一下就能夠了。

如何配置VS來調試.NET Framework源碼: https://referencesource.microsoft.com/#q=webhttps://technet.microsoft.com/zh-cn/cc667410.aspx

還一種方法是,下載.NET Reflector插件,該插件能夠幫助咱們在VS中直接調試dll,這種方式操做很是簡單,不過該插件收費,具體的能夠查看我以前寫過的文章(羣裏有該插件的註冊版)

.NET Core是什麼

有醜纔有美,有低纔有高,概念是比較中誕生的。.NET Core就是如此,它是其它操做系統的.NET Framework翻版實現。

操做系統不止Windows,還有Mac和類Linux等系統, .NET的實現 若是按操做系統來橫向分割的話,能夠分爲 Windows系統下的 .NET Framework 和 兼容多個操做系統的 .NET Core。

咱們知道,一個.NET程序運行核心在於.NET CLR,爲了能讓.NET程序在其它平臺上運行,一些非官方社區和組織爲此開發了在其它平臺下的.NET實現(最爲表明的是mono,其團隊後來又被微軟給合併了 ),但由於不是官方,因此在一些方面多少有些缺陷(如FCL),後來微軟官方推出了.NET Core,其開源在Github中,並被收錄在NET基金會(.NET Foundation,由微軟公司成立與贊助的獨立自由軟件組織,其目前收錄包括.NET編譯器平臺("Roslyn")以及ASP.NET項目系列,.NET Core,Xamarin Forms以及其它流行的.NET開源框架),旨在真正的 .NET跨平臺。

.NET Core是.NET 技術框架組成在Windows.macOS.Linux系統下的具體的實現。
.NET Core是一個開源的項目,其由 Microsoft 和 GitHub 上的 .NET 社區共同維護,但 這份工做仍然是巨大的,由於在早期對.NET上的定義及最初的實現一直是以Windows系統爲參照及載體,一些.NET機制實際上與Windows系統耦合度很是高,有些屬於.NET本身體系內的概念,有些則屬於Windows系統api的封裝。 那麼從Windows轉到其它平臺上,不只要實現相應的CLR,還要捨棄或重寫一部分BCL,於是,.NET Core在概念和在項目中的行爲與咱們日常有些不一樣。

好比,NET Core不支持AppDomains、遠程處理、代碼訪問安全性 (CAS) 和安全透明度,任何有關該概念的庫代碼都應該被替換。
這部分代碼它不只指你項目中的代碼,還指你項目中using的那些程序集代碼,因此你會在github上看到不少開源項目都在跟進對.NET Core的支持,而且不少開發者也嘗試學習.NET Core,這也是一種趨勢。

.NET Core指南https://docs.microsoft.com/en-us/dotnet/core/
.NET基金會:https://dotnetfoundation.org
.NET Core跨平臺的行爲變動:https://github.com/dotnet/corefx/wiki/ApiCompat
微軟宣佈.NET開發環境將開源 :https://news.cnblogs.com/n/508410/

.NET Standard是什麼

值得一提的是微軟還爲BCL提出了一個標準,畢竟各式各樣的平臺,技術層出不窮,爲了防止.NET在類庫方面的碎片化,即提出了一套正式的 .NET API (.NET 的應用程序編程接口)規範,.NET Standard。

正如上面CLS同樣,.NET Standard就相似於這樣的一個概念,不管是哪一個託管框架,咱們遵循這個標準,就能始終保持在BCL的統一性,即我不須要關心我是用的.NET Framework仍是.NET Core,只要該類被定義於.NET Standard中,我就必定能在對應支持的.NET Standard的版本的託管框架中找到它。

.NET Standard: https://docs.microsoft.com/zh-cn/dotnet/standard/net-standard#net-implementation-support

.NET Standard開源代碼:https://github.com/dotnet/standard

.NET官方開源項目連接

如今我將給出.NET相關的開源項目地址:
參與.NET和.NET開源項目的起點:https://github.com/Microsoft/dotnet

Visual Studio

在文章最後,我還要簡單的說下Visual Studio。

經過上文得知,只須要一個txt記事本+csc.exe咱們就能夠開發出一個.NET程序,那麼與之相比,.NET提供的開發工具VS有什麼不一樣呢?

咱們用記事本+csc.exe來編寫一個.NET程序只適合小打小鬧,對於真正要開發一個項目而言,咱們須要文件管理、版本管理、一個好的開發環境等。而vs ide則就是這樣一個集成代碼編輯、編譯、調試、追蹤、測試、部署、協做、插件擴展這樣多個組件的集成開發環境,csc.exe的編譯功能只是vs ide中的其中之一。使用vside開發能夠節省大量的開發時間和成本。

sln解決方案

當你用VS來新建一個項目時,VS會先爲你新建一個總體的解決方案。這個解決方案表現爲.sln和.suo後綴格式的文件,它們均是文本文件,對解決方案右鍵屬性能夠進行相應的修改,也能夠直接用記事本打開。

在sln中,定義瞭解決方案的版本及環境,如包含的項目,方案啓動項,生成或部署的一些項目配置等,你能夠經過修改或從新定義sln來更改你的整個解決方案。
而suo則包含於解決方案創建關聯的選項,至關於快照,儲存了用戶界面的自定義配置、調試器斷點、觀察窗口設置等這樣的東西,它是隱藏文件,可刪除但建議不要刪除。

咱們能夠經過對比各版本之間的sln來修改sln,也能夠使用網上的一些轉換工具,也能夠直接點擊VS的文件-新建-從現有代碼建立項目來讓項目在不一樣VS版本間切換。
Visual Studio 2010 - # Visual Studio 4.0
Visual Studio 2012 - # Visual Studio 4.0
Visual Studio 2013 - # Visual Studio 12.00
Visual Studio 2015 - # Visual Studio 14
Visual Studio 2017 - # Visual Studio 15

項目模板

VS使用項目模板來基於用戶的選擇而建立新的項目,也就是新建項目中的那些展現項(如mvc5項目/winform項目等等),具體表現爲包含.vstemplate及一些定義的關聯文件這樣的母版文件。將這些文件壓縮爲一個 .zip 文件並放在正確的文件夾中時,就會在展現項中予以顯示。

用戶能夠建立或自定義項目模板,也能夠選擇現有的模板,好比我建立一個控制檯項目就會生成一個在.vstemplate中定義好的Program.cs、AssemblyInfo.cs(程序集級別的特性)、App.config、ico、csproj文件

csproj工程文件

這裏面,csproj是咱們最多見的核心文件,CSharp Project,它是用於構建這個項目的工程文件。

csproj是基於xml格式的MSBuild項目文件,其仍然是文本文件,能夠打開並修改定義了的工程構造的屬性,好比選擇性的添加或刪除或修改包含在項目中的文件或引用、修改項目版本、將其轉換爲其它類型項目等。

MSBuild是微軟定義的一個用於生成應用程序的平臺(Microsoft Build Engine),在這裏爲VS提供了項目的構造系統,在微軟官方文檔上有着詳細的說明:https://msdn.microsoft.com/zh-cn/library/dd393573.aspxhttps://docs.microsoft.com/zh-cn/visualstudio/msbuild/msbuild

項目屬性雜項

如今,簡單說明一下csproj文件的一些核心元素。咱們用vs新建一個控制檯項目,而後對項目右鍵屬性打開項目屬性,在應用程序頁咱們能夠定義:程序集名稱(生成出來的程序集以程序集名稱做爲文件名,至關於csc中的/out)、默認命名空間(每次新建類裏面顯示的命名空間)、目標框架、應用程序類型、程序集信息(AssemblyInfo中的信息)、啓動對象(可同時存在多個Main方法,需指定其中一個爲入口對象)、程序集資源(一些可選的圖標及文件)

1.在生成頁有:

  • 條件編譯符號(全局的預編譯#define指令,不用在每一個文件頭部定義,至關於csc中的/define)
  • 定義DEBUG/TRACE常量(用於調試輸出的定義變量,如智能追蹤的時候能夠輸出該變量)
  • 目標平臺(指定當前面向什麼處理器生成的程序集,至關於csc中的/platform。選擇x86則生成的程序集生成32位程序,能在32/64位Intel處理器中使用。選擇x64則生成64位,只能在64位系統中運行。選擇Any CPU則32位系統生成32位,64位系統則生成64位。注意:編譯平臺和目標調用平臺必須保持一致,不然報錯。生成的32位程序集不能調用64位程序集,64位也不能調用32位)、首選32位(若是目標平臺是Any CPU而且項目是應用程序類型,則生成的是32位程序集)
  • 容許不安全代碼(unsafe開關,在c#中進行指針編程,如調換a方法和b方法的地址)
  • 優化代碼(至關於csc中的/optimize,優化IL代碼讓調試難以進行,優化JIT代碼)
  • 輸出路徑(程序集輸出目錄,可選擇填寫相對路徑目錄或絕對路徑目錄)
  • XML文檔文件(至關於csc中的/doc,爲程序集生成文檔註釋文件,瀏覽對方程序集對象就能夠看到相關注釋,VS的智能提示技術就運用於此)
  • 爲COM互操做註冊(指示託管應用程序將公開一個 COM 對象,使COM對象能夠與託管應用程序進行交互)

2.在高級生成設置中有:語言版本(能夠選擇C#版本)、調試信息(至關於csc中的/debug。選擇none則不生成任何調試信息,沒法調試。選擇full則容許將調試器附加到運行程序,生成pdb調試文件。選擇pdb-only,自.NET2.0開始與full選項徹底相同,生成相同的pdb調試文件。)、文件對齊(指定輸出文件中節的大小)、DLL基址(起點地址)

3.在生成事件選項中能夠設置生成前和生產後執行的命令行,咱們能夠執行一些命令。

4.在調試選項中有一欄叫:啓用Visual Studio承載進程,經過在vshost.exe中加載運行項目程序集,這個選項能夠增長程序的調試性能,啓用後會自動在輸出目錄生成{程序集名稱}.vshost.exe這樣一個文件,只有噹噹前項目不是啓動項目的時候才能刪除該文件。

IntelliTrace智能追溯

還要介紹一點VS的是,其IntelliTrace智能追溯功能,該功能最先存在於VS2010旗艦版,是我用的最舒服的一個功能。

簡單介紹,該功能是用來輔助調試的,在調試時可讓開發人員瞭解並追溯代碼所產生的一些事件,而且可以進行回溯以查看應用程序中發生的情形,它是一個很是強大的調試追蹤器,它能夠捕捉由你代碼產生的事件,如異常事件、函數調用(從入口)、ADO.NET的命令(Sql查詢語句...)、ASP.NET相關事件、代碼發送的HTTP請求、程序集加載卸載事件、文件訪問打開關閉事件、Winform/Webform/WPF動做事件、線程事件、環境變量、Console/Trace等輸出...

咱們能夠經過在調試狀態下點擊調試菜單-窗口-顯示診斷工具,或者直接按Ctrl+Alt+F2來喚起該功能窗口。

固然,VS還有其它強大的功能,我建議你們依次點完 菜單項中的 調試、體系結構、分析這三個大菜單裏面的全部項,你會發現VS真是一個強大的IDE。比較實用且方便的功能舉幾個例子:

好比 從代碼生成的序列圖,該功能在vs2015以前的版本能夠找到(https://msdn.microsoft.com/en-us/library/dd409377.aspxhttps://www.zhihu.com/question/36413876)

好比 模塊關係的代碼圖,能夠看到各模塊間的關係

好比 對解決方案的代碼度量分析結果

好比 調試狀態下 函數調用的 代碼圖,咱們能夠看到MVC框架的函數管道模型

以及並行堆棧狀況、加載的模塊、線程的實際狀況

還有如進程、內存、反彙編、寄存器等的功能,這裏再也不一一展現

連接

有關解決方案:https://msdn.microsoft.com/zh-cn/library/b142f8e7(v=vs.110).aspx
有關項目模板: https://msdn.microsoft.com/zh-cn/library/ms247121(v=vs.110).aspx
有關項目元素的說明介紹:https://docs.microsoft.com/zh-cn/previous-versions/visualstudio/visual-studio-2010/16satcwx(v%3dvs.100)
有關調試更多內容:https://docs.microsoft.com/zh-cn/visualstudio/debugger/
有關代碼設計建議:https://docs.microsoft.com/zh-cn/visualstudio/code-quality/code-analysis-for-managed-code-warnings
有關IntelliTrace介紹:https://docs.microsoft.com/zh-cn/previous-versions/visualstudio/visual-studio-2010/dd264915(v%3dvs.100)

建議

我熱愛編程。

我知道大多數人對技術的積累都是來自於日常工做中,工做中用到的就去學,用不到就不學,學一年的知識,而後用個五六年。
我也能理解人的理想和追求不一樣,有的人可能就想平淡點生活。有的人多是過了拼勁,習慣了安逸。有的人已經認命了。
而我如今也天天飽滿工做沒多少時間,但在下班之餘我仍然堅持天天都看一看書。
想學沒時間學,想拼不知道往哪拼。有埋汰本身腦殼笨的,有說本身不感興趣的。有明明躊躇滿志,但總三天捕魚兩天曬網的。我身邊的朋友大多都這樣。

我想說,儘管咱們每一個人的境遇、思想、規劃不一樣,但我確定你們大部分是出於生計而工做。
而出於生計,那就是爲了本身。而既然是爲了本身,那就別天天渾渾噩噩過,即便你因各類緣由而沒有鬥志。

編程來不得虛的,若是你沒走上管理,那麼你的技術好就是好,很差就是很差,混不得,一分技術一分錢。本身不紮實,你運氣就不可能太好。
技術是相通的,操做系統、通訊、數據結構、協議標準、技術規範、設計模式,語言只是門工具。要知其然也要知其因此然,只知道1個梨+1個梨=2個梨,不知道1個蘋果+1個蘋果等於啥就悲劇了。

那怎樣提高本身?確定不能像以前那樣被動的去學習了。
光靠工做中的積累帶來的提高是沒有多少。你不能靠1年的技術重複3年的勞動,本身不想提高就不能怨天尤人。
上班你們都同樣,我認爲成功與否取決於你的業餘時間。你天天下班不管再苦都要花一個小時來學習,學什麼都行,確定能改變你的人生軌跡。
好比你天天下班後都用一小時來學一個概念或技術點,那麼300天就是300個概念或者技術點,這是何等的恐怖。

固然,這裏的學要有點小方法小技巧的。不能太一條道摸到黑的那種,雖然這樣最終也能成功,而且印象還深入,可是總歸效率是有點低的。
好比你從網上下載個項目源碼,你項目結構不知道,該項目運用技術棧也不太瞭解,就一點一點的開始解讀。這是個提高的好方法,但這樣很累,能夠成功,可是很慢。見的多懂的少,每每會由於一個概念上的缺失而在一個細小的問題上浪費很長時間。或者說一直漫無目的的看博客來了解技術,那樣獲取的知識也不繫統。

個人建議是讀書,書分兩類,一類是 講底層概念的 一類是 講上層技術實現的。
能夠先從上層技術實現的書讀起(如何鏈接數據庫、如何寫網頁、如何寫窗體這些)。在有必定編程經驗後就從底層概念的書開始讀,操做系統的、通訊的、數據庫的、.NET相關組成的這些...
讀完以後再回過頭讀這些上層技術的書就會看的更明白更透徹,最後再琢磨git下來的項目就顯得輕鬆了。

就.NET CLR組成這一塊中文書籍比較少,由淺到深推薦的書有 你必須知道的.NET(挺通俗),CLR C#(挺通俗,進階必看),若是你想進一步瞭解CLR,能夠看看園子裏 包建強http://www.cnblogs.com/Jax/archive/2009/05/25/1488835.html 和中道學友http://www.cnblogs.com/awpatp/archive/2009/11/11/1601397.html翻譯的書籍及文章,固然若是你英語合格的話也能夠直接閱讀他們翻譯的來源書籍,我這裏有Expert .NET 2.0 IL Assembler的機器翻譯版,同時我也建議從調試的方面入手,如 NET高級調試(好多.NET文件調試、反編譯的文章都是參考這本書和Apress.Expert.dot.NET.2.0.IL.Assembler(這本書我有機器翻譯版)的內容)或者看看Java的JVM的文章。
歡迎加羣和我交流(書籍我都放在羣文件裏了)

如今技術發展很快,我建議你們有基礎的能夠直接看官方文檔,(詳細連接我已經在各小節給出)如下是部分經常使用總連接:

asp.net指南:https://docs.microsoft.com/zh-cn/aspnet/#pivot=core
Visual Studio IDE 指南:https://docs.microsoft.com/zh-cn/visualstudio/ide/
C# 指南: https://docs.microsoft.com/zh-cn/dotnet/csharp/
.NET指南:https://docs.microsoft.com/zh-cn/dotnet/standard/
微軟開發文檔:https://docs.microsoft.com/zh-cn/

最後送給你們我常常作的兩句話:
1.先問是否是,再問怎樣作,最後我必定會問 爲何
2.沒人比誰差多少,相信本身,堅持不斷努力,你也能成功

我喜歡和我同樣的人交朋友,不被環境影響,本身是本身的老師,歡迎加羣 .Net web交流羣, QQ羣:166843154 慾望與掙扎

做者:小曾
出處:https://www.cnblogs.com/1996V/p/9037603.html 歡迎轉載,但請保留以上完整文章,在顯要地方顯示署名以及原文連接。
.Net交流羣, QQ羣:166843154 慾望與掙扎

個人博客即將同步至騰訊雲+社區,邀請你們一同入駐:https://cloud.tencent.com/developer/support-plan?invite_code=367ug8kkmjk0s