C#(念作 See Sharp
)是一種簡單、現代、面向對象并且類型安全的編程語言。C# 源于 C 語言家族,因此 C、C++ 和 java 工程師們能迅速上手。ECMA 國際[1](ECMA International)發布的 ECMA-334 規范[2]和由國際標準化組織[3](ISO)及國際電工委員會[4](IEC)發布的 ISO/IEC 23270 規范使 C# 語言成為一種標準化的語言,微軟 .NET Framework C# 編譯器就是遵照了這兩個標準而實現的。
C# 不僅是一門面向對象的編程語言,同時它也為面向組件(component-oriented)[5]編程提供了支持。現代軟件設計越來越多地依賴于以自包含(self-contained)[6]與自描述(self-describing)[7]功能包形式的軟件組件。這些組件的關鍵之處在于它們呈現出一種帶有屬性(PRoperties)、方法(methods)和事件(events)的編程模型、對其進行說明的特性(attributes)并整合有它們自身的文檔。C# 所提供的語言結構能直接支持這些概念,使得 C# 語言能輕而易舉地玩轉軟件組件。
以下 C# 特點有助于構建魯棒性[8]與持久性的應用程序:垃圾回收機制(garbage collection)能自動回收內存中的無用對象,異常處理機制(exception handling)提供了結構化的可擴展的錯誤偵測與恢復方法,而類型安全(type-safe)的設計又使它不會去獲取到未被初始化的變量、獲得數組維度外的索引或者執行未經檢驗的類型強轉換等狀況。
C# 擁有一個統一類型系統(unified type system)。所有的 C# 類型,包括 int
和 double
在內的基元類型都繼承自同一個根: object
類型。因此,所有類型均能使用一組共用的操作方法且它們的值可以以一致的方式進行存儲、傳輸和操作。此外 C# 提供了支持用戶自定義的引用類型和值類型,不僅允許對輕量級的結構、還允許對對象動態分配。
為了確保 C# 程序和庫能以兼容的方式演變發展,C# 的設計非常強調版本管理(versioning)。在這方面其他許多編程語言并未投入過多關注,而結果是當新版本的依賴庫被引入后,那些編程語言寫就的程序往往會崩潰。在 C# 的設計方面,它立即就受到了版本管理的影響,考慮到了包括區分虛修飾符 virtual
與重載修飾符 override
、接口重載規則以及支持顯式地接口成員聲明等問題。
本章的剩余部分將描述 C# 語言的精華所在。通過接下來的章節將詳細地甚至會帶有數學精神地描述它們的規則與例外,本章節將力爭為讀者展現一幅簡潔明了的 C# 圖卷,目的是為了讓讀者通覽這門語言,以便讀者能早日編寫代碼并閱讀余下諸章。
Hello World
程序是每一門編程語言引言部分必有的傳統精彩項目。這是 C# 自家的 Hello World 程序:
using System;class Hello{ static void Main() { Console.WriteLine("Hello, World"); }}
C# 的源文件使用 .cs
作為其后綴名,因此 Hello, World
程序的代碼被存儲在 hello.cs
文件內,微軟的 C# 編譯器[9]能通過命令行
csc hello.cs
來實現對其編譯并生成一個名為 hello.exe
的可執行程序集[10]。這個程序集運行后將在屏幕上顯示為:
Hello, World
Hello, World
程序始于一個 using
指令,它用于引入命名空間 System
。命名空間提供了一個有層次有條理的 C# 程序和庫。命名空間包含了類型和其他命名空間,例如命名空間 System
就包含了 Console
,以及類似 IO
和 Collections
這樣的命名空間。通過 using
指令引入一個命名空間后將可以使用這個命名空間下的所有成員類型。因此在引入 System
命名空間后,程序可以使用 Console.WriteLine
而不必完整地輸入 System.Console.WriteLine
。
「Hello, World」程序的 Hello
類中只聲明了一個成員——一個名叫 Main
的靜態方法(static),實例方法可以使用保留關鍵字 this
來引用一個特定的包裝對象,但靜態方法不能。按照慣例,靜態方法 Main
是程序的入口點。命名空間 System
下 Console
類的 WriteLine
方法最后在屏幕上輸出了「Hello, World」字樣。這個類(Console)由 .NET Framework 類庫提供,并且通常是自動被微軟 C# 編譯器引用的。要注意,C# 并沒有和「運行時」庫分離開,恰恰相反,.NET Framework 自身就是一個 C# 的「運行時」庫。
C# 的組織概念之關鍵在于程序、命名空間、類型、成員以及程序集(assemblies),C# 的程序由至少一個文件組成,程序中聲明了包含有成員的類型并可命名空間化。類型有如類(Classes)和接口(Interfaces),成員則有如字段(Fields)、方法(Methods)、屬性(Properties)和事件(Events)。當 C# 程序被編譯,它們將被物理地打包到一個程序集中。程序集的后綴名如 .exe
或 .dll
,這取決于它們是實現了應用(applications)還是類庫(Libraries)。舉個例子:
using System;namespace Acme.Collections{ public class Stack { Entry top; public void Push(object data) { top = new Entry(top, data); } public object Pop() { if (top == null) throw new InvalidOperationException(); object result = top.data; top = top.next; return result; } class Entry { public Entry next; public object data; public Entry(Entry next, object data) { this.next = next; this.data = data; } } }}
上述代碼片段聲明了一個名曰 Stack
的類,其命名空間為 Acme.Collections
,因此它的完全限定名叫做 Acme.Collections.Stack
。這個類包含了好幾個成員:一個字段 top
、兩個方法 Push
與 Pop
以及一個內部類 Entry
。而內部類 Entry
則又包含了三個成員:兩個字段 next
與 data
和一個構造函數。如果源代碼被保存在 acme.cs
文件中,那么命令行輸入:
csc /t:library acme.cs
編譯這段代碼并使用參數 /t
來顯式地指明編譯為庫(代碼中不包含 Main 入口點),由此將生成名曰 acme.dll
的程序集。程序及所包含的可執行代碼由 IL(Intermediate Language)[11]指令和符號信息元數據(metadata)[12]構成。在被執行前,程序集內的 IL 代碼由 .NET CLR(Common Language Runtime)[13]自動即時編譯(JIT compiler,Just-In-Time compiler)轉換為針對處理器定制的代碼。
由于程序集是一種功能上包含有代碼和元數據的自描述單元,故 C# 不需要使用 #include
指令和頭文件(header files)。其內包含有公開類型和成員的特殊的程序集在程序被編譯時能被輕松引用。比如說,程序從 acme.dll
程序集中調用 Acme.Collections.Stack
類:
using System;using Acme.Collections;class Test{ static void Main() { Stack s = new Stack(); s.Push(1); s.Push(10); s.Push(100); Console.WriteLine(s.Pop()); Console.WriteLine(s.Pop()); Console.WriteLine(s.Pop()); }}
若程序被存于名為 text.cs
的文件內,則當其被編譯時,acme.dll
程序集需要使用參數 /r
來引入程序:
csc /r:acme.dll test.cs
如此,將創建名為 text.exe
的可執行程序集,并可運行且輸出接口如下:
100101
C# 允許程序的源代碼被保存在多個源文件內。當一個包含多個文件的 C# 程序被編譯,所有的源文件都會被一起處理且這些源文件能被直接相互引用,因為在被編譯處理之前,這些文件會被連接到一個更大的文件內。在 C# 中并不需要前置聲明(Forward Declaration),這是因為在大多數情況下聲明的順序是無所謂的。同時 C# 既不限制源代碼內是否只能聲明一個公開類型,也不限制源代碼的文件名必須與其內聲明的類型一致[14]。
在 C# 中有兩種類型:值類型和引用類型,值類型變量直接包含其自身數據,而引用類型(如對象等)則存其引用對象的內存地址。對于引用類型而言,極有可能出現兩個不同的變量指向同一個對象的情況,同時也極有可能出現因為修改了一個引用類型的對象的值導致另一個引用類型受到影響的情況。而對于值類型而言,每一個變量都是其值的副本,修改一個值對象的值不可能影響到其他值對象(除非使用了 ref
或 out
參數)。
C# 的值類型能被更進一步地分為簡單類型、枚舉類型、結構類型和可空類型,引用類型則可被進一步分為類、接口、數組和委托。下表可一覽 C# 的類型系統。
這八種整數類型提供了 8 位、16 位、32 位和 64 位的有符號與無符號的形式。
兩種浮點數類型:float
和 double
分別被 IEEE 754 規范描述為 32 位長度的單精度浮點數和 64 位的多精度浮點數。高精度類型 decimal
是一個 128 位長度的數據類型,適合被用于金融、財務與錢幣的計算。bool
被用于描述布爾值——一種即是或非的值。字符(character)和字符串(string)在 C# 中使用 Unicode 編碼。字符類型 char
描述為一個 UTF-16 代碼單元,而字符串類型 string
被描述為一個連續的 UTF-16 代碼單元。下表簡述了 C# 的數字類型:
在 C# 中創建一個新類型需要對其進行聲明(類型聲明,type declarations),并為其指定名稱與成員。C# 有五種用戶可定義的類型大類,它們分別是:類(class)、結構(struct)、接口(interface)、枚舉(enum)和委托(delegate)。
類所定義的數據結構包含了數據成員(字段)和函數成員(方法、屬性等)。類支持單繼承和多態性,派生類能擴展和特化基類。
結構是一種簡單的類,它代表了一種含有數據成員與函數成員的結構。然而,不像類,結構是值類型(而不是引用類型),不會去申請分配堆(heap)。結構不支持用戶指定的繼承,且所有的結構類型都隱式地繼承自 object
類型。
接口定義了功能成員的名稱集合的契約,實現了接口的類或結構必須提供接口中所定義的函數成員的實現。一個接口可以繼承自多個接口,一個類或結構可以繼承自多個接口。
委托類型定義了方法的參數列表和返回類型細節。委托將一個方法變為一個獨立實體,使其能被指定變量并如參數般被傳遞。委托與其他語言的方法指針很像,但不像后者,委托是面向對象(object-oriented)[15]且類型安全(type-safe)[16]的。
類、結構、接口和委托類型都支持泛型(generics)。
枚舉類型的名字是直接確定的,所有枚舉類型必須以八種整數類型之一為其基礎類型,枚舉值的集合即為其底層類型值的集合(筆者注:此處「集合」原文為「set」,指數學意義上的集合,意味著每一個成員都是唯一的,這與 array 或 collections 不同)。
C# 支持任意類型的一維或多維數組。與上面所講的類型不同,數組類型不需要在使用前聲明,而是在類型名稱后緊跟著方括號 []
的形式來創建。例如 int[]
是一個 int 類型的一維數組,int[,]
是 int 類型的二維數組,int[][]
是 int 類型的多維數組。
可空類型在使用前不需要聲明,每一種不可空的值類型 T
都對應一個可空類型的版本 T?
,后者在前者的基礎上增加了對 null
值的支持。舉例來說,int?
能被賦值為任意 32 位整數或 null
值。
C# 的任意類型的值都可被視作一個對象 object
。每一種類型都直接或間接地派生自 object
類,而 object
則是所有類型的基類。引用類型的值也就是其所指向的 object
對象的值。值類型的值之所以能被視作對象,是因為裝箱(boxing)[17]和<
新聞熱點
疑難解答