C#學習筆記——軟件構建與.NET平臺介紹

一 軟件構建,智者的積木

  多年來,衆多的業界鉅子給軟件構建做出了不同的比喻,雖然你可能並不認識他們,也不打算了解這些巨人的英雄史詩,但是你應該瞭解他們對軟件構建的看法,畢竟他們纔是這場遊戲中的高級玩家。我並不想討論這些隱喻是否合理,我只希望以下隱喻能夠幫助你重新思考下軟件構建活動。

  寫作 這一隱喻暗示着軟件開發過程是一種以編寫代碼爲主的代價昂貴的試錯過程,而非仔細的規劃和設計。

  耕種 這一隱喻暗示了軟件就想是耕種一樣,你每次只處理它的一小部分,一點一點的加到整個系統,使系統一點一點的“生長”。它也暗示了,工作應該按部就班,正如春種秋收一樣,各個環節有強硬的邏輯存在。

  蓋房子 這一隱喻形象的說明了規劃和設計在構建活動中的重要性,同時也暗示了軟件在初期修復缺陷的成本要遠遠低於在後期的修復成本。

  焦油坑 經典書籍《人月神話》的第一章提到的:“大型系統的開發就猶如這樣一個焦油坑,很多大型和強壯的動物在其中劇烈的掙扎,他們中大多數開發出了可運行的系統——不過只有極少數的項目滿足了目標、進度和預算的要求。”

  不管你是否同意以上觀點,它們確實在一定時期影響了一部分人。而我想從一名程序員的角度來說我對軟件構建的認識。我覺得軟件構建過程對於程序員來說,就像是在玩樂高積木。衆多的語言給我們提供了形形色色的API,就如同不同形狀、顏色的積木塊。業界標準構成了積木之間的接點,使積木塊之間無縫銜接。單個的小積木通過接點組合成你想要的任何形狀和功能的大的模塊,模塊和模塊之間再相互組合,形成更大的系統。軟件構建與玩積木兩者都是考驗腦力的智力活動,你可以按照圖紙玩積木(別人設計的),也可以用積木來拼裝你想要的任何東西(自己做設計)。(ps.我小時候用樂高的“潛水艇”積木拼出了星際I中的“歌利亞”。)也就是說,語言、平臺就在那裏,你能做出什麼,就看你能想到什麼,以及你的團隊能實現什麼。

  軟件構建是一項十分複雜的工作,絕不是一個人的工作,而是一件團隊活動。下面,給出軟件構建的最普遍活動:

  • 定義問題
  • 需求分析
  • 規劃構建
  • 軟件架構
  • 詳細設計
  • 編碼與調試
  • 單元測試
  • 集成測試
  • 集成
  • 系統測試
  • 保障維護

  以上活動在整個軟件構建過程中會反覆、交替進行,這些活動僅僅是基於技術的產品導向過程,而沒有包括相應的管理活動。對於程序員來說,編碼與調試往往佔據了我們的大部分時間。而其他活動隨項目的類型和規模,往往有所“裁剪”。可見,軟件構建過程十分複雜,正因爲其複雜性,人們爲了確保軟件項目的成功,試圖尋找一套結構化方法。但是到目前爲止,也沒有找到能夠保證軟件項目100%成功的方法。雖然軟件項目中,“聖盃”並不存在,但是我們仍然得到了相當有用的方法或指南,以提高項目成功機率,並使這種成功得以再現,例如軟件工程理論,PMI制定的項目管理指南(PMBOK)以及敏捷開發的相關思想等等。

二 語言沒有“銀彈”正如世間難尋“聖盃”

  如果把開發語言當成一門外語的話,那麼我們程序員無疑掌握了衆多的“外語”,即使外語翻譯也望塵莫及。正如外語一樣,不同語言有不同的特點,要造就了不同的陣營與擁護者。在不同陣營,並相互抨擊的時候,你是否還猶豫不決,是否又有轉移方向的衝動?某位業界大牛說過,好的程序員要掌握3類語言:一、快速開發的語言;二、吃飯的語言;三、最鍾愛的語言。這只是3類語言,並不是說3種。你完全可以學習一門語言並說它在這3個方面都很出色。(ps.目前我看PHP有這個苗頭。)還有一種說法就是“學得多,學的淺“。不管你是想深入掌握一門語言還是要廣泛瞭解多門語言,你一定要認識到開發語言沒有所謂的“銀彈”,或許某些語言能獨擋一面,當沒有哪種語言是全才,其必有優勢與劣勢,而且隨着時間的推移,今日風光的語言,明日可能就會被淘汰。如果你鍾愛一門語言就不要被路人輕易打動而另投他人門派。學習語言是一件持之以恆的事情,當你想改換門派的時候不妨問問,這門語言真的好嗎,你真的深入理解了你現在的語言了嗎,它真的是你想要的嗎?編程之真諦在於思想,透過語言編程纔是正確的道路。

  每個人的經歷不同可能會有不同的語言基礎,我在衆多語言之中選擇了C#,我並不認爲C#對所有人都適合,但它的諸多優點讓我難以放棄。以下是我選擇C#的理由。

  .Net平臺非常強大 .NET平臺提供了豐富的類庫,涵蓋了幾乎所有領域的應用,不需要藉助其它語言,就能開出幾乎所有的應用。當然,大量的API也需要我們不斷的學習。

  強大的開發環境 Visual Studio 可謂是IDE中的王者,用了之後就會被其強大的功能所吸引,在結合微軟的TFS,在軟件項目管理中將發揮出極大的作用。

  豐富的資源 微軟的MSDN,微軟社區還有大量的參考書籍,及龐大的用戶羣,造就了一個龐大的資源寶庫,你身處於這樣龐大的羣體之中,能夠更快更容易的掘取知識。

  入門簡單 C#本身就是一門面向對象的高級語言。相比於低級語言,其語法和代碼可讀性更高,更容易讓人接受,你完全可以通過書籍自學成才。

  開發週期快 C#或者說.NET的類庫已經爲我們做了大量的工作,相比其它語言,你的代碼量更少,你開發系統的週期更短,無論是大型項目還是小型項目你都會得益於這種快速的開發。當然,開發週期短也得益於Visual Studio 的強大功能。 

  與各種微軟產品的無縫集成 你可以和微軟的其它產品例如SQL Server,Office,Sharepoint,OC等無縫集成。

  當然,C#也有一些劣勢:

  不跨平臺 C#只能用於開發.NET程序,雖然.NET被設計爲平臺無關,但目前還無法在Linux等平臺上穩定運行.NET程序,雖然有個開源的Mono,但其可靠性有待測試。這會讓你失去很大的發揮空間,即使你能構建客戶需要的軟件,如果客戶堅持使用Linux系統你也只能放棄。或許有那麼一天,.NET可以用於其它平臺,但現在我們還是在Windows平臺玩吧。

  開源資源相對較少 相比其他語言,用C#寫的開源項目還是少數,有些時候你只能自己寫框架了。

  綜上所述,語言沒有“銀彈”,如果你享受C#給你帶來的便捷,同時接受了它的不足,並決定深入學習的話,我希望下面的文章能對你有所幫助。

三 .NET平臺結構

  從程序員的角度看,.NET可以理解爲一個運行庫環境(mscoree.dll)和一個全面的基類庫(mscorlib.dll)。如下圖所示:

(一)基類庫

  .Net Framework 中包含了Framework 類庫(Framework Class Library,FCL),有些書籍中也稱爲BCL,微軟正以驚人的速度在完善FCL,爲我們構建大型系統提供便利。如果想全面瞭解FCL,我推薦的書籍是《C#高級編程》,目前已經出到第七版了。該書以頁數和全面著稱,但不足之處在於,其內容比較基礎,要想深入學習,需結合其它書籍。FCL十分龐大,而且在不斷完善中,最好有針對性的學習某一分支,避免將精力分散。

(二)公共語言運行時

  運行時(runtime),可以理解爲執行給定編譯代碼單元所需的外部服務的集合。如上圖所示,.NET的運行時叫CLR(Common Language Runtime),即公共語言運行時,是一個可由多種開發語言使用的“運行時”。CLR不關心使用何種開發語言,只要相應的開發語言的編譯器面向CLR即可。微軟已經創建了幾個面向CLR的語言,包括:C++/CLI、C#、VB、F#、Python、Ruby,以及自己的編譯器。其它機構也制定了一些面向“CLR”的開發語言和編譯器。但是,使用最廣泛的還是微軟的C#語言。CLR的核心功能包括:內存管理,程序集加載,安全性,異常處理和線程同步等等。通常在CLR的控制下運行的代碼稱爲託管代碼。源代碼的編譯和執行過程如下:

1 源代碼編譯

  編譯的第一步——把源代碼編譯爲託管模塊(其中,C++的編譯器比較特殊,它能同時生成託管和非託管代碼,並生成到同一模塊中。)

  託管模塊 託管模塊是一個標準32(64)位Windows可移植載體PE32(PE32+)文件,需要CLR才能執行。託管模塊的組成如下:

  元數據 元數據是一組數據表。其中一些數據表描述了模塊中定義的內容,比如類及其成員。還有一些元數據描述了託管代碼模塊引用的內容,比如引用的類及成員。 這些元數據描述了每一個二進制文件中定義的類型(類、結構、枚舉等),以及每個類型的成員(屬性、方法、事件等)。Visual Studio 等開發工具通過元數據實現智能感知,而元數據也成爲包括WCF、Web Service、反射、晚期綁定和對象序列化等技術的支柱。

  MSIL Microsoft Intermediate Language (MSIL)微軟中間語言,也被叫做CIL(公共中間語言),或者簡稱IL。其本質上是.NET平臺的母語。任何一種支持.NET的語言在邏輯上都需要支持IL的,也都會被編譯爲IL。

  IL具有以下特徵:

  • 面向對象和使用接口
  • 值類型和引用類型之間的巨大差異
  • 強數據類型
  • 使用異常來處理錯誤
  • 使用特性

  IL的好處:

  • 語言集成性——每種支持.NET的語言生成的是幾乎相同的IL
  • 平臺無關性

  我們不需要直接使用IL來編寫程序,但是在.NET中,使用System.Reflect.Emit這個命名空間提供的類型可以在開發出在運行時能夠在內存中產生.NET程序集的程序,即“動態程序集”。由於在構造程序集時需要使用專有的IL指令集,所以如果要使用這部分功能開發軟件,需要掌握IL。

  編譯的第二步——將託管模塊合併爲程序集

  CLR不和模塊一起工作,它只和程序集一起工作。程序集是一個或多個模塊/資源文件的邏輯性分組,是重用、安全性及版本控制的最小單元。通過程序集的概念,我們可以把一組文件當做一個文件來對待。將託管模塊合併成程序集的過程如下:

  程序集包含的足夠的信息,使其具有自描述性。CLR能判斷出爲了執行程序集中的代碼,程序集的直接依賴對象是什麼,不需要註冊表或 Active Directory Domain Services(ADDS)中保存額外的信息。所以,相較於非託管組件,程序集更容易部署。

2 通用類型系統與公共語言規範

  公共類型系統與公共語言規範是,是.NET實現語言互操作性的基石。語言互操作性的真正含義是用一種語言編寫的類應該能直接與另一種語言編寫的類通信,特別是:

  • 用一種語言編寫的類應該能繼承用另一種語言編寫的類。
  • 一個類應該能包含另一個類的實例,而不管它們是使用什麼語言編寫的。
  • 一個對象應該能直接調用其它語言編寫的另一個對象的方法。
  • 對象應該能在方法之間的傳遞。
  • 在不同的語言之間調用方法時,應能在調試器中調試這些方法調用,即調試不同語言編寫的源代碼。

2.1 CTS

  類型(type)指的是集合{類,接口,結構,枚舉,委託}裏的任意一個成員。CLR完全是圍繞類型展開的,微軟制定了一個正式的規範,即"通用類型系統"(Common Type System,CTS),它描述了類型的定義和行爲。CTS規定,一個類型可以包含零個或多個成員,制定了類型的可視性規則和類型成員的訪問類型,爲類型的繼承、虛方法、對象生存期定義了相應的規則。我們無論使用哪一種語言,類的行爲都是完全一致的,因爲最終是由CTS來定義類的行爲。

  CTS定義了5種類型:

  • 類類型
  • 接口類型
  • 結構類型
  • 枚舉類型
  • 委託類型

  上述5種類型包含衆多的類型成員即集合{構造器,析構器,靜態構造函數,嵌套類型,運算符,方法,屬性,索引器,字段,只讀字段,常量,事件}中的元素之一。

  CTS還定義了一個內容豐富的類型層次結構,其中包含設計合理的位置,在這些位置上,代碼允許定義它自己的類型。CTS的層次結構如下:

  此外,CTS建立了一套定義明確的核心數據類型,以字符串類型爲例:String(VB.NET)、string(C#)、String^(C++/CLI)最終被解釋成爲CTS數據類型System.String。

2.2 CLS

  爲了能適用多種語言,微軟定義了一個“公共語言規範”(Common Language Specification,CLS),它詳細描述了一個最小的功能集,任何編譯器生成的類型想要兼容由其他符合CLS、面向CLR的語言所生成的組件,就必須支持這個最小功能集。由於IL是一種豐富的語言,大多數編譯器的編寫人員有可能把給定編譯器的功能限制爲只支持IL和CLS提供的一部分特性。對於CLS,首先是各個編譯器的功能不必強大到支持.NET的所有功能;其次,CLS提供瞭如下保證:如果限制類只能使用CLS兼容特性,就要保證其它兼容語言編寫的代碼可以使用這個類。編寫非CLS兼容代碼是完全可以接受的,只是在編寫了這種代碼後,就不能保證編譯好的IL代碼完全支持語言的互操作性。也就是說,在開發類型和方法的時候,如果希望它們對外“可見”,能夠從符合CLS的任何一種編程語言中訪問,就必須遵守由CLS定義的規則。用一種語言定義一個類型時,如果希望在另一個語言中使用該類型,就不要在該類型的 public 和 protected 成員中使用位於CLS外部的任何功能。否則,其它其它語言可能無法正常訪問這個類型的成員。以下代碼定義了一個不符合CLS的類型:

複製代碼
using System;
//檢查CLS相容性
[assembly:CLSCompliant(true)]

namespace CLRTest
{
    //因爲是public類,所以驗證相容性
    public sealed class Test
    {
        //返回類型不符合CLS
        public UInt32 get()
        {
            return 0;
        }

        //僅大小寫不同標識符,不符合CLS
        public string Get()
        {
            return "0";
        }

        //私有方法可以不符合CLS,不會顯示警告
        private UInt32 GET()
        {
            return 0;
        }
    }
}
複製代碼

  [assembly:CLSCompliant(true)]特性應用於程序集,這個特性告訴編譯器檢查 public 類型是否符合CLS規範。這種方法的優點是使用CLS兼容性的限制只適用於公共和受保護的類的成員和公共類。在類的私有實現方式中,可以編寫非CLS代碼,因爲其它程序集中的代碼不能訪問這部分代碼。

  CLS的基本規則是“一個類型的每個成員要麼是一個字段(數據),要麼是一個方法(行爲)。”編譯器遇到枚舉、數組、屬性、索引器、委託、事件、構造器、析構器、操作符重載、轉換操作符等任何一種構造,必須將其轉換成字段和方法,使CLR和其它語言能夠訪問這些構造。(注:CLR的完整規則列表,請參考http://msdn.microsoft.com/zh-cn/library/a2c7tshk.aspx。)

3 加載CLR

  生成的程序集既可以是一個可執行應用程序,也可以是一個DLL。理論上,任何基於IL的程序集都可以在任何CPU上運行,但是實際上,程序集有可能是不可移植的(64位程序不能運行在32位平臺上)。如果IL中沒有與特定CPU架構或機器語言相關的內容,JIT編譯器就會在運行時爲目標CPU生成機器指令。如果開發一個需要特定CPU架構的程序集,我們必須把CPU信息告訴VS,以便它將信息合併到二進制文件中。運行一個可執行文件時,Windows會檢查這個EXE文件的頭,判斷應用程序是32位的還是64位的(64位Windows提供了WoW64技術,運行運行32位程序,但有性能損耗)。Windows還會檢查頭文件中嵌入的CPU架構信息,確保當前計算機符合要求。Windows檢查後完EXE文件的頭,決定創建32位、64位還是WoW64進程之後,會再進程的地址空間中加載mscoree.dll(CLR中最重要的部分,稱爲“公共對象運行庫執行引擎”,它包含大量核心類型,它們封裝了各種常見的編程任務與核心數據類型)的x86、x64或IA64版本。然後進程的主線程調用mscoree.dll中定義的一個方法。這個方法初始化CLR,加載EXE程序集,然後調用其入口方法(Main)。隨即,託管的應用程序將啓動並運行。

4 執行程序集的代碼

4.1 程序集執行過程

  如前所述,程序集包含元數據與IL。爲了執行一個方法,首先必須把IL轉換成本地CPU指令。這是CLR的JIT(just-in-time,即時)編譯器(也叫“JITter”)的職責。下圖展示了,方法在被調用時,發生的事情:

  在Main方法執行之前,CLR會檢測出Main的代碼引用的所有類型。這導致CLR分配一個內部數據結構,它用於管理對所有引用的類型的訪問。上圖中,Main方法引用了一個Console類,這導致CLR分配一個內部結構。在這個內部結構中,Console類定義的每個方法都有一個對應的記錄項(entry)。每個記錄項都容納了一個地址,根據此地址即可找到方法是實現。對這個結構初始化時,CLR將每個記錄項都設置成(指向)包含在CLR內部的一個未文檔化的函數,即上圖中的JITCompiler(《CLR Via C#》中使用的名稱)。Main方法首次調用WriteLine時,JITCompiler函數會被調用。JITCompiler函數負責將一個方法的IL代碼編譯成本地CPU命令,根據運行環境不同,JIT編譯器會生成相應的x86、x64或IA64指令。然後,JITCompiler函數會在定義(該類型的)程序集的元數據中查找被調用的方法的IL。接着JITCompiler驗證IL代碼,並將IL代碼編譯爲本地CPU命令。本地CPU命令被保存到一個動態分配的內存塊中。然後JITCompiler返回CLR爲類型創建的內部數據結構,找到與調用方法對應的那一條記錄,修改最初對JITCompiler的引用,讓他現在指向內存塊中的代碼(WriteLine(string)的具體實現)。這些代碼執行完畢並返回時,會返回到Main中的代碼,然後繼續執行。

  當執行到Console.WriteLine("World");時,Main要再次調用WriteLine(string)。這時,因爲之前已經對WriteLine(string)的代碼進行了驗證和編譯,所以會直接執行內存塊中的代碼,WriteLine("World")執行完畢後,再次回到Main。

4.2 JITter

  JIT編譯器並不是把整個程序一次編譯完,而是隻編譯它調用的那部分代碼。代碼編譯過一次後,得到的內部可執行代碼就存儲起來,直到應用程序終止,編譯好的代碼就纔會丟失。所以,如果再次運行程序,或者啓動了程序的兩個實例(使用兩個不同的進程),JIT編譯器都要再次編譯IL。一個方法只有在首次調用時纔會造成一些性能損耗,之後對該方法的所有調用都以本地代碼的形式全速運行。由於編譯過程的最後一部分是在運行時進行的,JIT編譯器能夠確切地知道程序運行在什麼類型的處理器上,可以利用該處理器提供的任何特性或特定的機器代碼指令來優化最後的可執行代碼,以提高性能。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章