You are on page 1of 50

微軟電子書苑 第 1 頁,共 50 頁

導讀
C#是由C和C++語言所衍生出來的一種簡單、現代、物件導向且安全的程式語言,C#(發音為C-sharp)發
源於於C和C++語言家族中,所以對於C和C++的程式設計師而言可以說是非常熟悉的。C#語言是結合了
Visual Basic的高生產力以及C++語言的強大功能所發展出來的。

C#是微軟公司推出Visual Studio 7.0中的一部分,除了C#之外,Visual Studio還包括了Visual Basic、Visual


C++、Visual J# .NET和一些Script語言,例如:VB Script和Jscript,以上這些語言皆為發展微軟.NET平台的
有力工具。微軟的.NET平台包括了共用執行引擎和豐富的物件類別庫,另外.NET平台定義了 共同語言規
範 (CLS),它能讓各種支援 共同語言規範 的語言與 物件類別庫 安全地運作。對C#的開發者來說,這意謂著雖
然C#是一種新的語言,但是它能使用其他開發工具開發出來的物件類別庫函式庫,例如Visual Basic、Visual
C++。C#本身並沒有包含 物件類別庫 部分。

接下來將敘述C#語言的基本特色。下一章將介紹C#語言的規則、例外以及一些數學理論上的規則,本章將
求清楚及簡潔的說明。本章的目的在提供閱讀者一個C#語言的入門介紹,讓讀者能夠容易地閱讀接下來的章
節。

開始動手了
標準的"Hello World"程式如下:

using System;
class Hello
{
static void Main() {
Console.WriteLine("hello, world");
}
}

這段C#程式碼需要被存成一個副檔名為.CS的檔案,例如hello.cs。存成檔案之後,使用Visual Studio提供的
命令模式編譯器,亦可在一般的命令模式直接將程式編譯完成。接著在命令列執行下列的敘述:

csc hello.cs

編譯完成後會產生一個執行檔hello.exe,執行此程式所得的結果如下:

hello, world

這段程式的意義如下:

z using System代表引用一個名為system的namespace,這是由微軟.NET Framework


所提供的物件類別庫,此物件類別庫包含了Main方法中console型別的物件。
Namespace將物件類別庫中的各元素有組織地組成階層式的架構。程式中

http://www.e-msbooks.com/Reader/paser.asp?isbn=957-819-017-4&ch=201 2002/1/27
微軟電子書苑 第 2 頁,共 50 頁

的"using"代表引用namespace中的某個型別。這個"hello world"程式使用簡
寫"Console.WriteLine"來取代"System.Console.WriteLine"的寫法,其實二者意思相
同。
z Main方法是這個類別中的一員。因為有static修飾語,所以這個型別不需要實際先建
立一個執行個體。
z 程式的進入點,也就是程式開始處理的地方,通常是一個static的方法並命名為
Main,簡單的說,我們寫的程式都以Main作為進入點。
z "hello world"的輸出是靠物件類別庫來產生的。C#並沒有擁有自己的類別函式庫,它
使用.NET共用的類別函式庫,例如Visual Basic和Visual C++的函式庫。對於C和
C++的程式設計師來說,這段"hello world"的程式應該非常熟悉,並沒有出現什麼值
得注意的事情。
z 這段程式使用Main方法的範圍並非全域,方法和變數的範圍不支援全域宣告,通常
這些東西會包含在型別的宣告中,例如:類別和結構的宣告。
z 程式不支援"::"或"->"的運算子。C#完全不支援"::"運算子,至於"->"運算子則只能用於
少部分的程式中。另外句點"."通常用於存取物件,例如Console.WriteLine,即表示
WriteLine是屬於Console物件的一個方法。
z 程式中沒有包含向前的宣告,因為宣告的順序是沒有意義的。
z 程式中並沒有用到#include來引入別段程式碼,程式中的附屬程式通常是象徵意義的
處理而不是逐字地處理,而且C#也可以引用其他語言所寫的程式,例如Console這樣
的類別可以是由任何一種語言所寫成。

型別
C#支援二種型別:實值型別(value type)和參考型別(reference types)。實值型別包含了簡單的型別
(如字元、整數和浮點數)、enum型別(enum types)和結構型別(struct types);參考型別包含了類別
型別(class type)、介面型別(interface types)和delegate型別(delegate types),還有陣列型別
(array types)。

實值型別與參考型別不一樣的地方是實值型別擁有自己的資料,而參考型別的變數則是指向別的物件的資
料。參考型別的物件可以同時擁有兩個實值的資料,而且藉由改變其中一個實值進而間接地改變另一個實
值,實值型別的物件則擁有自己獨立的資料,即使參考它的實值改變也不會影響本身的資料。

範例:

using System;
class Class1
{
public int Value = 0;
}

http://www.e-msbooks.com/Reader/paser.asp?isbn=957-819-017-4&ch=201 2002/1/27
微軟電子書苑 第 3 頁,共 50 頁

class Test
{
static void Main() {
int val1 = 0;
int val2 = val1;
val2 = 123;
Class1 ref1 = new Class1();
Class1 ref2 = ref1;
ref2.Value = 123;
Console.WriteLine("Values: {0}, {1}", val1, val2);
Console.WriteLine("Refs: {0}, {1}", ref1.Value, ref2.Value);
}
}

上面的範例比較了二者的差異,程式的結果如下:

Values: 0, 123
Refs: 123, 123

程式中區域變數val1不會影響區域變數val2的值,因為這二個區域變數都是實值型別(整數型別),且每個
區域變數都存有自已的值,而ref2.Value = 123;的語法,則同時會影響ref1和ref2二個物件。

Console.WriteLine("Values: {0}, {1}", val1, val2);


Console.WriteLine("Refs: {0}, {1}", ref1.Value, ref2.Value);

上面二行程式碼展示了如何利用Console.WriteLine方法顯示字串,事實上這牽涉到變數的傳遞,在第一行中
的第一個參數是字串型別,它包含像{0}和{1}這樣的取代字元。每一個取代字元會指向傳入的參數,像
{0}即指向第二個參數val1,{1}即指向第二個參數val2,依此類推,在輸出到螢幕之前,每一個取代字
元都一直保留其所指向的參數。

程式設計師可以利用列舉(enum)和結構(struct)的宣告來定義新的實值型別,也可以經由類別、介面的
宣告來定義參考型別的函數,如下面範例:

using System;
public enum Color
{
Red, Blue, Green
}
public struct Point
{
public int x, y;

http://www.e-msbooks.com/Reader/paser.asp?isbn=957-819-017-4&ch=201 2002/1/27
微軟電子書苑 第 4 頁,共 50 頁

}
public interface IBase
{
void F();
}
public interface IDerived: IBase
{
void G();
}
public class A
{
protected virtual void H() {
Console.WriteLine("A.H");
}
}
public class B: A, IDerived
{
public void F() {
Console.WriteLine("B.F, implementation of IDerived.F");
}
public void G() {
Console.WriteLine("B.G, implementation of IDerived.G");
}
override protected void H() {
Console.WriteLine("B.H, override of A.H");
}
}
public delegate void EmptyDelegate();

這個範例示範了每種資料型別的宣告,在接下來的小節中,我們將更詳細的介紹型別宣告的方法。

內建型別

C#語言提供了一種內建型別(predefined type),這個部分對C和C++的程式設計師來說絕不陌生。它可以
是物件與字串兩種型別,物件型別是所有其他型別的基礎型別;字串型別則是用來代表Unicode的字串值。

內建型別包含了sign和unsigned的整數型別(integral types)、單精浮點型別(floating point)、和布林


(bool)、字元及小數的型別。sign整數型別有s位元組型別、短整數型別、整數型別和長整數型別,而
unsigned的整數型別有u位元組型別、短整數型別、整數型別和長整數型別。單精浮點型別則有單一形態以
及雙精浮點形態,布林型別用於表示布林值,亦即為"true"及"false"兩種狀態。

http://www.e-msbooks.com/Reader/paser.asp?isbn=957-819-017-4&ch=201 2002/1/27
微軟電子書苑 第 5 頁,共 50 頁

利用布林變數可以讓程式碼變得結構化,而且程式設計師也不會發生應該使用"= ="的時候卻使用"="的情形,
如此一來可降低程式的出錯風險。例如:

int i = ...;
F(i);
if (i = 0) // Bug: the test should be (i = = 0)
G();

這程式是無效的,因為if敘述式需要的是布林型別,而範例中的卻不是布林型別。

Char型別是用來表示Unicode字元。一個Char型別的變數可用來表示一個16-bit的單碼字元。

範例中的decimal適合用於表示計算時的誤差,因為單精浮點數無法表示。計算稅率以及貨幣換算即是使用
deciaml的最佳範例。decimal形態提供28個位數。

下表列出內建型別的內容及特性,以及表示法的寫法:

Type Description Example


Object The ultimate base type of all other types object o = null;
String type; a string is a sequence of Unicode
String string s = "hello";
characters
Sbyte 8-bit signed integral type sbyte val = 12;
Short 16-bit signed integral type short val = 12;
Int 32-bit signed integral type int val = 12;

Long 64-bit signed integral type long


val1 = 12;long val2 = 34L;

Byte 8-bit unsigned integral type


byte val1 = 12;byte val2 = 34U;

ushort 16-bit unsigned integral type ushort val1 = 12;ushort val2 =


34U;

Uint 32-bit unsigned integral type


uint val1 = 12;uint val2 = 34U;

ulong val1 = 12;


ulong 64-bit unsigned integral type ulong val2 = 34U;
ulong val3 = 56L;
ulong val4 = 78UL;
Float Single-precision floating point type float val = 1.23F;

http://www.e-msbooks.com/Reader/paser.asp?isbn=957-819-017-4&ch=201 2002/1/27
微軟電子書苑 第 6 頁,共 50 頁

double Double-precision floating point type double val1 = 1.23;double val2 =


4.56D;
Boolean type; a bool value is either true or bool
Bool
false val1 = true;bool val2 = false;
Character type; a char value is a Unicode
Char char val = 'h';
character
decimal Precise decimal type with 28 significant digits decimal val = 1.23M;

這裡所顯示的型別皆為簡略的表示法,例如關鍵字int為System.Int32的簡寫法,為了節省撰寫程式時的麻
煩,簡寫法當然是比完整寫法要方便多了。

內建型別,例如int只用在一些特別的情況,但是大部分內建型別的使用會類似於結構中的用法。覆載
(overloading)運算子的功能可讓程式設計師定義新的結構型別(struct types),這種情況非常類似於內建
型別的實值。舉個例子,Digit可以用於和內建型別的整數相同的數學運算子,也可以定義Digit和內建型別互
相轉換。

內建型別可在運算覆載方法時使用,例如一個比較的運算子等於("= =")和不等於("!=")中,內建型別即
有二種完全不同的語意:

z 當兩個整數型別都相同於一個同樣的正實值時,代表兩個整數型別相同。
z 當兩個物件的型別都相同於同一個物件,或者是都等於null時,代表兩個物件的型別
相同。
z 當兩個字串型別相同於一個同樣的字串且其中的整數都相同,或者都等於null時,代
表兩個字串相同。

範例如下:

class Test
{
static void Main() {
string s = "Test";
string t = string.Copy(s);
Console.WriteLine(s = = t);
Console.WriteLine((object)s = = (object)t);
}
}

執行結果:

http://www.e-msbooks.com/Reader/paser.asp?isbn=957-819-017-4&ch=201 2002/1/27
微軟電子書苑 第 7 頁,共 50 頁

True
False

因為第一個比較式所比較的是二個字串,而第二個則是比較二個物件,因此會有這樣的執行結果。

轉換(Conversion)

內建型別也可以轉換成別的型別。例如,內建型別的int和long可互相轉換。C#中區別了二種轉換:隱含式型
別轉換(implicit conversion)和顯式型別轉換(explicit conversion)。隱含式型別轉換不需要很仔細的檢查
即可轉換,例如將int轉換成long即是屬於隱含式型別轉換。這種轉換通常都能成功,而轉換的結果也不會有
資料的流失。經由隱含式型別轉換能夠很輕易地完成型別的轉換,如下面的範例:

using System;
class Test
{
static void Main() {
int intValue = 123;
long longValue = intValue;
Console.WriteLine("{0}, {1}", intValue, longValue);
}
}

此例即純粹地將int型別轉換為long型別。

相較之下,顯式型別轉換則需要經過轉型運算才能完成,舉例如下:

using System;
class Test
{
static void Main() {
long longValue = Int64.MaxValue;
int intValue = (int) longValue;
Console.WriteLine("(int) {0} = {1}", longValue, intValue);
}
}

此例即是使用顯式型別轉換將long型別轉換為int型別,輸出結果如下:

(int) 9223372036854775807 = -1

因為有溢位(overflow)的情況發生,所以會導致以上的結果。轉型運算允許使用隱含式型別轉換和顯式型

http://www.e-msbooks.com/Reader/paser.asp?isbn=957-819-017-4&ch=201 2002/1/27
微軟電子書苑 第 8 頁,共 50 頁

別轉換。

陣列型別

陣列(Array)可為一維陣列或是多維陣列。支援了"rectangular"以及"jagged"陣列。一維陣列(Single-
dimensional arrays)是最基本的,所以是一個很好的學習開始點。

以下為範例:

using System;
class Test
{
static void Main() {
int[] arr = new int[5];

for (int i = 0; i < arr.Length; i++)


arr[i] = i * i;
for (int i = 0; i < arr.Length; i++)
Console.WriteLine("arr[{0}] = {1}", i, arr[i]);
}
}

此例建立了一個實值型別為一維的陣例,給予陣列元素初始值,然後將它們顯示出來。這段程式執行的輸出
結果為:

arr[0] = 0
arr[1] = 1
arr[2] = 4
arr[3] = 9
arr[4] = 16

上例中int[]寫法即表示這是一個陣列,陣列型別是一個接一個填入的。範例如下:

class Test
{
static void Main() {
int[] a1; // single-dimensional array of i
int[,] a2; // 2-dimensional array of int
int[,,] a3; // 3-dimensional array of int
int[][] j2; // "jagged" array: array of (arr
int[][][] j3; // array of (array of (array of int))

http://www.e-msbooks.com/Reader/paser.asp?isbn=957-819-017-4&ch=201 2002/1/27
微軟電子書苑 第 9 頁,共 50 頁

}
}

程式中有區域變數的宣告,這些區域變數為陣列型別,而其中的陣列元素皆是整數資料。

陣列型別亦為參考型別的一種,陣列變數宣告後,電腦會配置一個空間給陣列使用。陣列的產生要先利用建
立陣列的陳述式建立出陣列,同時要給予初始值,範例如下:

class Test
{
static void Main() {
int[] a1 = new int[] {1, 2, 3};
int[,] a2 = new int[,] {{1, 2, 3}, {4, 5, 6}};
int[,,] a3 = new int[10, 20, 30];
int[][] j2 = new int[3][];
j2[0] = new int[] {1, 2, 3};
j2[1] = new int[] {1, 2, 3, 4, 5, 6};
j2[2] = new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9};
}
}

此例列出了建立陣列的陳述式。變數a1、a2和a3表示出它屬於一種矩形的陣列,而變數j2則是jagged陣列。
用形狀來形容陣列的形態是再貼切也不過的了,矩形的陣列通常是四方形的,只要給予陣列每一維的長度,
即可清楚地知道這個陣列有多大。例如a3為三維的陣列,每個維度分別為10、20及30,即可知道這個陣列總
一共有10*20*30個元素。

相較之下,陣列j2為一個鋸齒狀、有缺角的陣列,也可稱為是陣列中的陣列。j2在範例中為一個int陣列中的
陣列,換句話說是一個int[]中的一個一維陣列。這二個陣列可以分別給予個別的個數,像j2[0]有三個元素,j2
[1]有六個元素,而j2[2]有九個元素。

陣列的元素和陣列的形狀-不論它是矩形的或是鋸齒形的,也不管它是幾維的-都是陣列形態的一部分。另
一方面,陣列的大小-即它每一維的長度,並不算是陣列形態的一部分。這在語法上有清楚的劃分,陣列每
一維的長度,是在建立的陳述式上明確表示的,而不是在陣列的形態上表示。

下面為宣告陣列的範例:

int[,,] a3 = new int[10, 20, 30];

int[,,]是表示陣列的型別為整數型別的陣列。new int[10, 20, 30]則是建立陣列的陳述式。

在區域變數的宣告中簡略的寫法是被允許的,因為不需要重覆地宣告陣列的形態。

http://www.e-msbooks.com/Reader/paser.asp?isbn=957-819-017-4&ch=201 2002/1/27
微軟電子書苑 第 10 頁,共 50 頁

以下即是範例:

int[] a1 = new int[] {1, 2, 3};

以上寫法可以簡略地寫成:

int[] a1 = {1, 2, 3};

而程式不必有任何語法上的改變。

上面提到的陣列的初始例如{1, 2, 3}的寫法即是用於給予這個陣列初始值。例如:

class Test
{
static void Main() {
short[] a = {1, 2, 3};
int[] b = {1, 2, 3};
long[] c = {1, 2, 3};
}
}

此程式表示同樣的陣列初始化語法,可以用於不同的陣列形態。因為範例中必須決定陣列的初始值,但不可
能在該程式區塊中設定陣列的初始值。

Type system unification

C#提供了一種稱為"unified type system"的功能。所有的型別-包含實值型別-都是從object型別衍生出來


的,這使我們可以呼叫任何物件程序中的任何值,比如整數。範例如下:

using System;
class Test
{
static void Main() {
Console.WriteLine(3.ToString());
}
}

呼叫型別轉換程序。

範例如下:

class Test

http://www.e-msbooks.com/Reader/paser.asp?isbn=957-819-017-4&ch=201 2002/1/27
微軟電子書苑 第 11 頁,共 50 頁

{
static void Main() {
int i = 123;
object o = i; // boxing
int j = (int) o; // unboxing
}
}

這個例子非常有趣,一個整數的實值能夠轉換為物件型別(object)然後再回復為整數型別。這個範例顯示
出boxing和unboxing。當一個實值型別的變數需要被轉換為參考型別時,一個box物件即分配去容納這個
值,然後這個值再被複製到下一個box中;unboxing則是相反的。當一個物件box要回到它原來的實值型別,
它的值將會從這個box複製出來然後進入一個適宜的儲存位置。型別系統不透過覆寫的方法即可做型別轉
換,對程式來說,它們並不需要int扮演和object相同的角色,這種能力是一經要求就可擁有的。這種能力就
好像是溝通實值型別和參考型別的一座橋,這二者(實值型別和參考型別)之間的隔閡長久存在於大部分的
程式語言中。舉例來說,一個堆疊(stack)類別可以提供Push和Pop二種程序去取得以及回復object的實
值。

public class Stack


{
public object Pop() {...}
public void Push(object o) {...}
}

此例顯示因C#具有unified type system,所以Stack類別可以用於任何型別,包括整數(int)實值型別。

變數和參數
變數(Variable)是指在電腦中可以儲存資訊的儲存位置,每個變數都擁有一種型別,可得知這個變數它能
儲存的值是什麼。區域變數(Local variables)是在程序、屬性或指標中所宣告的。區域變數通常被指定為
一個特定的型別及宣告為特定的變數名稱,並給予隨意的值,例如:

int a;
int b = 1;

而區域的變數宣告也可以並列宣告,例如a和b的宣告可以寫成像下面這樣:

int a, b = 1;

變數的值必須在運算前宣告。見下面範例:

class Test
{
static void Main() {

http://www.e-msbooks.com/Reader/paser.asp?isbn=957-819-017-4&ch=201 2002/1/27
微軟電子書苑 第 12 頁,共 50 頁

int a;
int b = 1;
int c = a + b;
...
}
}

上面範例中的寫法是無效的,因為變數在運算前並沒有宣告值,這個規則支配的詳細情形,在後面的章節會
有敘述。

field指的是與類別(class)、結構(struct)有關的變數、或是指類別或結構的執行個體。一個field的宣告是
利用static修飾語去定義靜態變數。靜態field與型別有關,而執行個體變數則是與執行個體有關。

接下來的範例使用了System.Data:

class Employee
{
private static DataSet ds;
public string Name;
public decimal Salary;
...
}

程式表示一個Emplyee的class,它擁有一個private的靜態變數和二個public的執行個體變數。

正式的參數宣告也就是定義變數。參數(parameter)有四種:實值參數(value parameter)、參考參數
(reference parameter)、輸出參數(output parameter)以及參數陣列(parameter array)。

實值參數用於參數傳遞的過程中,當一個引數的實值傳入一個程序中時,參數的修飾語沒有對引數產生影
響。實值參數與它所擁有的變數有關,這與引數相對應的變數有所區別。範例如下:

using System;
class Test {
static void F(int p) {
Console.WriteLine("p = {0}", p);
p++;
}
static void Main() {
int a = 1;
Console.WriteLine("pre: a = {0}", a);
F(a);

http://www.e-msbooks.com/Reader/paser.asp?isbn=957-819-017-4&ch=201 2002/1/27
微軟電子書苑 第 13 頁,共 50 頁

Console.WriteLine("post: a = {0}", a);


}
}

程式表示有一個名稱為F的程序擁有一個叫做p的實值參數。這段程式執行後的結果為:

pre: a = 1
p = 1
post: a = 1

參考參數用於傳址參數"by reference"傳遞時,參數會去扮演程式呼叫者(caller-provided)的引數。參考參
數自己並不定義變數,確切地說它是參照相對應的引數,參考參數是利用ref修飾子來宣告的。

範例:

using System;
class Test {
static void Swap(ref int a, ref int b) {
int t = a;
a = b;
b = t;
}
static void Main() {
int x = 1;
int y = 2;

Console.WriteLine("pre: x = {0}, y = {1}", x, y);


Swap(ref x, ref y);
Console.WriteLine("post: x = {0}, y = {1}", x, y);
}
}

程式顯示有一個叫Swap的程序擁有二個參考參數。執行結果輸出如下:

pre: x = 1, y = 2
post: x = 2, y = 1

ref這個保留字必須在宣告及使用這個參數時用到,在這個範例中ref呼叫參數使程式設計師便於閱讀程式碼且
了解引數並不能改變呼叫的結果。

輸出參數和參考參數十分相似,不同的是caller-provided引數的初始值對輸出參數並不重要,輸出參數是利

http://www.e-msbooks.com/Reader/paser.asp?isbn=957-819-017-4&ch=201 2002/1/27
微軟電子書苑 第 14 頁,共 50 頁

out修飾子來宣告,範例如下:

using System;
class Test {
static void Divide(int a, int b, out int result, out int remainder) {
result = a / b;
remainder = a % b;
}
static void Main() {
for (int i = 1; i < 10; i++)
for (int j = 1; j < 10; j++) {
int ans, r;
Divide(i, j, out ans, out r);
Console.WriteLine("{0} / {1} = {2}r{3}", i, j, a
}
}
}

程式表示一個名稱為Divide的程序中擁有二個輸出參數,一個參數是除法的結果,另一個是餘數。

對實值參數、來源參數和輸出參數來說,都有和caller-provided引數一對一的對應參數來代表它們。而參數
列則允許多對一的關係,例如多個引數可以同時代表一個參數陣列,換句話說,參數陣列允許包含多個引
數。

參數陣列利用params修飾子來宣告,一個程序中只能有一個參數陣列,通常是最後指派的參數。參數陣列的
型別通常是一維陣列的型別。呼叫陣列型別中的元素時,可以呼叫一或多個引數。範例如下:

using System;
class Test
{
static void F(params int[] args) {
Console.WriteLine("# of arguments: {0}", args.Length);
for (int i = 0; i < args.Length; i++)
Console.WriteLine("\targs[{0}] = {1}", i, args[i]);
}
static void Main() {
F();
F(1);
F(1, 2);
F(1, 2, 3);

http://www.e-msbooks.com/Reader/paser.asp?isbn=957-819-017-4&ch=201 2002/1/27
微軟電子書苑 第 15 頁,共 50 頁

F(new int[] {1, 2, 3, 4});


}
}

程式表示一個程序F中須傳入一些int型別參數,以及類似的程序。

範例如下:

# of arguments: 0
# of arguments: 1
args[0] = 1
# of arguments: 2
args[0] = 1
args[1] = 2
# of arguments: 3
args[0] = 1
args[1] = 2
args[2] = 3
# of arguments: 4
args[0] = 1
args[1] = 2
args[2] = 3
args[3] = 4

這個範例中大部分是展示在Console類別中使用WriteLine程序的寫法。引數在程序中的轉換,以下的程式可
以作為範例:

int a = 1, b = 2;
Console.WriteLine("a = {0}, b = {1}", a, b);

上例完整地使用了參數陣列。WriteLine方法提供許多覆載的方法以便讓不同的參數可以傳入,並執行適當的
程序。

namespace System
{
public class Console
{
public static void WriteLine(string s) {...}
public static void WriteLine(string s, object a) {...}
public static void WriteLine(string s, object a, object b) {...}

http://www.e-msbooks.com/Reader/paser.asp?isbn=957-819-017-4&ch=201 2002/1/27
微軟電子書苑 第 16 頁,共 50 頁

...
public static void WriteLine(string s, params object[] args) {.
}
}

記憶體手動管理
記憶體手動管理(Manual memory management)需要程式開發人員手動去分配記憶體。手動控制記憶體不
但花時間而且相當困難,因此C#提供了自動管理記憶體功能使得開發人員免除了這個惱人的工作。在大部分
的情況下,記憶體自動管理的功能使得程式開發人員有時間加強程式碼的品質並提升開發程式的效率,而且
在效能表現上並沒有任何負面的影響。

範例如下:

using System;
public class Stack
{
private Node first = null;
public bool Empty {
get {
return (first = = null);
}
}
public object Pop() {
if (first = = null)
throw new Exception("Can't Pop from an empty Stack.");
else {
object temp = first.Value;
first = first.Next;
return temp;
}
}
public void Push(object o) {
first = new Node(o, first);
}
class Node
{
public Node Next;
public object Value;

http://www.e-msbooks.com/Reader/paser.asp?isbn=957-819-017-4&ch=201 2002/1/27
微軟電子書苑 第 17 頁,共 50 頁

public Node(object value): this(value, null) {}


public Node(object value, Node next) {
Next = next;
Value = value;
}
}
}

這個範例表示了一個類別Stack中連結了一連串的執行個體Node。執行個體Node在使用Push時會被產生,
用過後便成為不需要的垃圾。當執行個體Node不再被其他程式呼叫後便會被當作垃圾而蒐集,舉例來說,當
一個項目從Stack中移開之後,相關聯的執行個體Node便成為垃圾而被蒐集。

範例如下:

class Test
{
static void Main() {
Stack s = new Stack();
for (int i = 0; i < 10; i++)
s.Push(i);
s = null;
}
}

這個範例呼叫了一個測試的類別Stack,Stack被建立並初始成十個實值s為null的元件。當變數s設成null時,
Stack以及相關的十個執行個體Node便成為垃圾,垃圾蒐集器允許立即清除這些垃圾,但並沒有必要立刻清
除。大部分的程式開發人員不會見到C#潛在的垃圾蒐集器自動將不使用的物件自記憶體中刪除。程式開發人
員平常可以讓CLR自動管理記憶體,但若有時需要自己控制效能時,C#亦提供了使用"unsafe"程式碼的功
能。這樣的程式碼可以直接控制指標的形態以及物件的位址,C#需要程式設計師將這些物件被垃圾蒐集器移
除前移至其他地方暫存。

這些unsafe程式碼事實上對程式開發者以及使用者來說都是種不安全的做法。使用unsafe程式碼時,程式碼
中必須將"unsafe"關鍵字明顯的標示出來,所以程式開發者並不會不小心使用到unsafe程式碼,而且編譯器
以及執行引擎會共同確認之,因此unsafe程式碼不會被誤認為一般程式碼執行,這些嚴格的限制使得unsafe
程式碼在使用時是值得被信任的。

範例如下:

using System;
class Test
{
unsafe static void WriteLocations(byte[] arr) {

http://www.e-msbooks.com/Reader/paser.asp?isbn=957-819-017-4&ch=201 2002/1/27
微軟電子書苑 第 18 頁,共 50 頁

fixed (byte *p_arr = arr) {


byte *p_elem = p_arr;
for (int i = 0; i < arr.Length; i++) {
byte value = *p_elem;
string addr = int.Format((int) p_elem, "X");
Console.WriteLine("arr[{0}] at 0x{1} is {2}", i
p_elem++;
}
}
}
static void Main() {
byte[] arr = new byte[] {1, 2, 3, 4, 5};
WriteLocations(arr);
}
}

這個範例表示了一個稱為WriteLocations的程式碼,該程式碼是種unsafe程式碼,決定了一個矩陣且利用指
標去重複操縱其中的元件。其中的每個矩陣元件的index、value以及location都寫入記憶體。這個程式可能的
輸出結果如下:

arr[0] at 0x8E0360 is 1
arr[1] at 0x8E0361 is 2
arr[2] at 0x8E0362 is 3
arr[3] at 0x8E0363 is 4
arr[4] at 0x8E0364 is 5

當然,正確的記憶體位置可能依據執行程式的不同而有所差異。

運算式(Expression)
C#包含單位元運算子、雙位元運算子以及三元運算子。下面的表格整理了所有的運算子,並將之以運算優先
順序排列。

Category Operators
Primary (x) x.y f(x) a[x] x++ x-- new typeof sizeof checked unchecked
Unary + - ! ~ ++x --x (T)x
Multiplicative */%
Additive +-
Shift << >>

http://www.e-msbooks.com/Reader/paser.asp?isbn=957-819-017-4&ch=201 2002/1/27
微軟電子書苑 第 19 頁,共 50 頁

Relational < > <= >= is


Equality = = !=
Logical AND &
Logical XOR ^
Logical OR |
Conditional AND &&
Conditional OR ||
Conditional ?:
Assignment = *= /= %= += = <<= >>= &= ^= |=

當一個運算式包含了多個運算子,每個運算子的優先順序會決定它在運算式執行的優先順序。舉例來說,運
算式x+y*z會被當作x+(y*z)運算,由於運算子*是比運算子+優先運算的。

當發生多個運算子的優先順序皆相同時,就要靠運算式中運算子的相關位置來決定運算的優先順序。

除了被指定的運算子之外,所有雙位元的運算子被認為是左相關(left-associative)的,表示運算式會從左
至右運算。舉例來說,x+y+z會當成(x+y)+z來運算。

被指定的運算子以及表示狀況的運算子會被認為是右相關(right-associative)的,表示運算是會從右至左運
算。舉例來說,x=y=z會被當作x=(y=z)來表示。

運算以及相關性可以利用括號來控制。舉例來說,x+y*z會先運算y乘上z,然後再加上x得其值,但是(x + y) *
z會先將x加上y後再乘上z做運算。

敘述式(Statement)
C#直接引用了許多C以及C++的敘述,然而還是有些值得注意的地方。下面的表格列出了一些可使用的敘
述,並且都有提供範例。

敘述式 範例
static void Main() {
F();
G();
Statement
{
lists和block
H();
敘述式
I();
}
}

static void Main(string[] args) {

http://www.e-msbooks.com/Reader/paser.asp?isbn=957-819-017-4&ch=201 2002/1/27
微軟電子書苑 第 20 頁,共 50 頁

if (args.Length = = 0)
goto done:
Console.WriteLine(args.Length);
done:
Labeled
Console.WriteLine("Done");
statements
}
和goto敘述
Local constant declarations static void Main() {

const float pi = 3.14;
const int r = 123;
Console.WriteLine(pi * r * r);
}
static void Main() {
int a;
Local
int b = 2, c = 3;
variable
a = 1;
declarations
Console.WriteLine(a + b + c);
}
static int F(int a, int b) {
return a + b;
Expression }
敘述式 static void Main() {
F(1, 2); // Expression statement
}
static void Main(string[] args) {
if (args.Length = = 0)
Console.WriteLine("No args");
if敘述式
else
Console.WriteLine("Args");
}

static void Main(string[] args) {


switch (args.Length) {
case 0:
Switch敘述 Console.WriteLine("No args");
式 break;
case 1:
Console.WriteLine("One arg ");
break;

http://www.e-msbooks.com/Reader/paser.asp?isbn=957-819-017-4&ch=201 2002/1/27
微軟電子書苑 第 21 頁,共 50 頁

default:
int n = args.Length;
Console.WriteLine("{0} args", n);
break;
}
}
static void Main(string[] args) {
int i = 0;
while (i < args.length) {
while敘述式 Console.WriteLine(args[i]);
i++;
}
}
static void Main() {
string s;
do敘述式 do { s = Console.ReadLine(); }
while (s != "Exit");
}
static void Main(string[] args) {
for (int i = 0; i < args.length; i++)
for敘述式
Console.WriteLine(args[i]);
}
static void Main(string[] args) {
foreach敘述 foreach (string s in args)
式 Console.WriteLine(s);
}
static void Main(string[] args) {
int i = 0;
while (true) {
break敘述 if (i > args.Length)
式 break;
Console.WriteLine(args[i++]);
}
}

static void Main(string[] args) {


int i = 0;

http://www.e-msbooks.com/Reader/paser.asp?isbn=957-819-017-4&ch=201 2002/1/27
微軟電子書苑 第 22 頁,共 50 頁

while (true) {
Console.WriteLine(args[i++]);
if (i > args.Length)
continue敘
continue;
述式
break;
}
}
static int F(int a, int b) {
return a + b;
}
return敘述
static void Main() {

Console.WriteLine(F(1, 2));
return;
}
static int F(int a, int b) {
if (b = = 0)
throw new Exception("Divide by zero");
return a / b;
}
throw敘述 static void Main() {
式以及try敘 try {
述式 Console.WriteLine(F(5, 0));
}
catch(Exception e) {
Console.WriteLine("Error");
}
}
static void Main() {
int x = 100000, y = 100000;
checked和
Console.WriteLine(unchecked(x * y));
unchecked
Console.WriteLine(checked(x * y)); // Error
敘述式
Console.WriteLine(x * y); // Error
}

static void Main() {


lock敘述式 A a = ...
lock(a) {
a.P = a.P + 1;

http://www.e-msbooks.com/Reader/paser.asp?isbn=957-819-017-4&ch=201 2002/1/27
微軟電子書苑 第 23 頁,共 50 頁

}
}
using System;
class Resource: IDisposable
{
public void F() {
Console.WriteLine("Resource.F");
}
public void Dispose() {...}
using敘述式
...
}
static void Main() {
using(Resource r = new Resource()) {
r.F();
}
}

類別(Class)
宣告一個類別可以將之定義成新的參考型別。類別可以讓其他的類別繼承,而且可以實作其提供的介面。類
別的成員可包含常數、領域(fields)、方法、屬性、索引、事件、運算子、建構子、解構子以及巢狀式敘
述,每個成員關連著一個程式碼的文字區域,得以控制相關的成員。有五個控制權限的形式如下表所示。

形式 代表的意義
public 全域,權限沒有限制
protected 限制在本身的類別中使用或是從本身的類別中取得的型別才能使用
internal 限制在本身的程式中使用
protected internal 限制在這個程式中使用或是該類別中的型別才能使用
private 限制在本身的型別中使用

範例如下:

using System;
class MyClass
{
public MyClass() {
Console.WriteLine("Constructor");
}

http://www.e-msbooks.com/Reader/paser.asp?isbn=957-819-017-4&ch=201 2002/1/27
微軟電子書苑 第 24 頁,共 50 頁

public MyClass(int value) {


MyField = value;
Console.WriteLine("Constructor");
}
MyClass() {
Console.WriteLine("Destructor");
}
public const int MyConst = 12;
public int MyField = 34;
public void MyMethod(){
Console.WriteLine("MyClass.MyMethod");
}
public int MyProperty {
get {
return MyField;
}
set {
MyField = value;
}
}
public int this[int index] {
get {
return 0;
}
set {
Console.WriteLine("this[{0}] = {1}", index, value);
}
} public event EventHandler MyEvent;
public static MyClass operator+(MyClass a, MyClass b) {
return new MyClass(a.MyField + b.MyField);
}
internal class MyNestedClass
{}
}

上面範例表示一個類別可以包含有各式各樣的成員。

參考範例如下:

http://www.e-msbooks.com/Reader/paser.asp?isbn=957-819-017-4&ch=201 2002/1/27
微軟電子書苑 第 25 頁,共 50 頁

class Test
{
static void Main() {
// Constructor usage
MyClass a = new MyClass();
MyClass b = new MyClass(123);
// Constant usage
Console.WriteLine("MyConst = {0}", MyClass.MyConst);
// Field usage
a.MyField++;
Console.WriteLine("a.MyField = {0}", a.MyField);
// Method usage
a.MyMethod();
// Property usage
a.MyProperty++;
Console.WriteLine("a.MyProperty = {0}", a.MyProperty);
// Indexer usage
a[3] = a[1] = a[2];
Console.WriteLine("a[3] = {0}", a[3]);
// Event usage
a.MyEvent += new EventHandler(MyHandler);
// Overloaded operator usage
MyClass c = a + b;
}
static void MyHandler(object sender, EventArgs e) {
Console.WriteLine("Test.MyHandler");
}
internal class MyNestedClass
{}
}

上面的範例表示了這些成員的用法。

常數(Constant)

常數是指一個類別中不會變的值,這個值可以在程式編譯時被運算。只要程式中沒有被重複參考引用的話,
變數是允許被設定成其他常數的值。影響常數表現的規則請參考常數運算(constant expression)一節。

class Constants

http://www.e-msbooks.com/Reader/paser.asp?isbn=957-819-017-4&ch=201 2002/1/27
微軟電子書苑 第 26 頁,共 50 頁

{
public const int A = 1;
public const int B = A + 1;
}

上面的範例表示一個名叫Constants的類別擁有兩個public的常數。

因為常數本身已經是不變的了,所以不需要使用static修飾語。常數可經由類別來控制,請見下面範例:

class Test
{
static void Main() {
Console.WriteLine("{0}, {1}", Constants.A, Constants.B);
}
}

這個類別程式顯示了常數A以及常數B的值。

領域(Field)

所謂領域(field)是個與物件或是類別相關聯的變數成員。請見下面範例:

class Color
{
internal ushort redPart;
internal ushort bluePart;
internal ushort greenPart;
public Color(ushort red, ushort blue, ushort green) {
redPart = red;
bluePart = blue;
greenPart = green;
}
...
}

上面範例中呈現了一個類別Color,其中包含一個名叫redPart、greenPart以及bluePart的內部執行個體領
域。領域也可以被宣告成靜態的,如下面範例:

class Color
{
public static Color Red = new Color(0xFF, 0, 0);

http://www.e-msbooks.com/Reader/paser.asp?isbn=957-819-017-4&ch=201 2002/1/27
微軟電子書苑 第 27 頁,共 50 頁

public static Color Blue = new Color(0, 0xFF, 0);


public static Color Green = new Color(0, 0, 0xFF);
public static Color White = new Color(0xFF, 0xFF, 0xFF);
...
}

上面範例中便呈現了靜態的領域如Red、Blue、Green以及White。

靜態的領域在這說明並不十分合適,領域通常在還沒使用時被定義,但在定義後便沒有辦法阻止其他使用者
改變它們。上述程式的Color方法中,可能會誤以為其實值永遠不會改變而發生不可預測的錯誤,唯讀領域可
以用來避免這類型的錯誤。唯讀領域的安排只能在程式剛開始宣告時,或是在同一個類別的建構子中定義,
這樣類別Color就可以藉此而成為靜態的領域。

class Color
{
internal ushort redPart;
internal ushort bluePart;
internal ushort greenPart;
public Color(ushort red, ushort blue, ushort green) {
redPart = red;
bluePart = blue;
greenPart = green;
}
public static readonly Color Red = new Color(0xFF, 0, 0);
public static readonly Color Blue = new Color(0, 0xFF, 0);
public static readonly Color Green = new Color(0, 0, 0xFF);
public static readonly Color White = new Color(0xFF, 0xFF, 0xFF);
}

方法(Method)

方法是在類別或物件中用來描述計算或是特殊功能的一個成員。方法中含有許多正式的參數(亦可能沒
有)、設定的變數,方法也可能是靜態或非靜態的。宣告為靜態的方法可以經由類別存取,至於非靜態的方
法可以經由類別執行個體存取。請見下面範例:

using System;
public class Stack
{
public static Stack Clone(Stack s) {...}
public static Stack Flip(Stack s) {...}
public object Pop() {...}

http://www.e-msbooks.com/Reader/paser.asp?isbn=957-819-017-4&ch=201 2002/1/27
微軟電子書苑 第 28 頁,共 50 頁

public void Push(object o) {...}


public override string ToString() {...}
...
}
class Test
{
static void Main() {
Stack s = new Stack();
for (int i = 1; i < 10; i++)
s.Push(i);
Stack flipped = Stack.Flip(s);
Stack cloned = Stack.Clone(s);
Console.WriteLine("Original stack: " + s.ToString());
Console.WriteLine("Flipped stack: " + flipped.ToString());
Console.WriteLine("Cloned stack: " + cloned.ToString());
}
}

上面這個範例展示了一個類別Stack,其中擁有數個靜態的方法(Clone以及Flip)與數個非靜態的方法
(Push、Pop以及Tostring)。

方法是可以被重覆的,這代表不管任何方法只要擁有獨特的特徵便能擁有同一個名字,所謂方法的特徵可以
是方法的名字、數字以及本身包含的變數。方法中可識別的特徵並不包含回傳的型別,請見下面範例:

class Test
{
static void F() {
Console.WriteLine("F()");
}
static void F(object o) {
Console.WriteLine("F(object)");
}
static void F(int value) {
Console.WriteLine("F(int)");
}
static void F(int a, int b) {
Console.WriteLine("F(int, int)");
}
static void F(int[] values) {

http://www.e-msbooks.com/Reader/paser.asp?isbn=957-819-017-4&ch=201 2002/1/27
微軟電子書苑 第 29 頁,共 50 頁

Console.WriteLine("F(int[])");
}
static void Main() {
F();
F(1);
F((object)1);
F(1, 2);
F(new int[] {1, 2, 3});
}
}

上面的程式表示一個F方法,這程式的輸出結果如下:

F()
F(int)
F(object)
F(int, int)
F(int[])

屬性(Property)

屬性可以用來改變一個物件或一個類別的成員。舉例來說,屬性可以包含描述線的長度、字體的大小以及視
窗的標題等等。屬性是領域的自然延伸,而且兩者的語法也很相同。然而與領域不同的地方在於屬性不會表
示儲存的位置,但是屬性擁有可用來讀取或紀錄其實值的運算子。

屬性可以透過property關鍵字來宣告,宣告屬性的第一步與領域的宣告很類似,第二步則包含了一個get運算
子或者(加上)一個set運算子。在下面的範例中,類別Button定義了一個屬性Caption。

public class Button


{
private string caption;
public string Caption {
get {
return caption;
}
set {
caption = value;
Repaint();
}
}

http://www.e-msbooks.com/Reader/paser.asp?isbn=957-819-017-4&ch=201 2002/1/27
微軟電子書苑 第 30 頁,共 50 頁

屬性是可以被讀取以及寫入的,就像屬性Caption中包含了讀取get以及設定set兩個運算子。當讀取屬性的實
值時,get運算子就會被呼叫,當寫入屬性的實值時,set運算子也會被呼叫。set運算子中,新的實值是由稱
為value的參數所給予。

屬性實際上的值是在被使用時才會設定,而不是在宣告時設定。屬性Caption,可以用和領域同樣的方法被讀
取以及寫入。

Button b = new Button();


b.Caption = "ABC"; // set; causes repaint
string s = b.Caption; // get
b.Caption += "DEF"; // get & set; causes repaint

事件(Event)

事件是個讓物件或類別可以觸發的成員。事件可以藉由在類別中以event關鍵字來宣告,雖然增加了event關
鍵字,但事件的宣告還是有點類似領域宣告,還有要注意這種宣告的型別必須是delegate型別。

請參考下面範例:

public delegate void EventHandler(object sender, System.EventArgs e);


public class Button
{
public event EventHandler Click;
public void Reset() {
Click = null;
}
}

類別Button定義了一個EventHandler型別的Click事件。在類別Button中,Click即是EventHandler型別,然而
在類別Button之外時,成員Click只能在運算子+=以及-﹦的左邊使用。運算子+=代表新增對事件的操作,而
運算子-=則代表移除事件的操作。範例如下:

using System;
public class Form1
{
public Form1() {
// Add Button1_Click as an event handler for Button1's Click eve
Button1.Click += new EventHandler(Button1_Click);
}
Button Button1 = new Button();

http://www.e-msbooks.com/Reader/paser.asp?isbn=957-819-017-4&ch=201 2002/1/27
微軟電子書苑 第 31 頁,共 50 頁

void Button1_Click(object sender, EventArgs e) {


Console.WriteLine("Button1 was clicked!");
}
public void Disconnect() {
Button1.Click -= new EventHandler(Button1_Click);
}
}

上面例子展示了一個類別Form1,並為Button1的Click事件增加了稱作Button1_Click的事件操作。這個事件
操作在方法Disconnect中便被移除了。

簡單的事件宣告如下:

public event EventHandler Click;

上列程式碼中,編譯器自動提供了潛在的+=以及-=運算子。

我們可以藉由提供明確的新增或移除運算子而擁有更多的控制能力,上面的類別Button可以加
上"add"或"remove"控制子後,重寫如下面所示:

public class Button


{
private EventHandler handler;
public event EventHandler Click {
add { handler += value; }
remove { handler -= value; }
}
}

上面的改變對於用戶端程式並沒有幫助,但是卻使得類別Button更準確的完成。

運算子(Operator)

運算子代表可以定義為具運算能力且被類別中的常數接受的成員。有三種可以被定義的運算子:單位元運算
子、雙位元運算子以及轉型運算子。

下面的範例定義了代表變數0到9之間的十進位數字的decimal digit。

using System;
public struct Digit
{

http://www.e-msbooks.com/Reader/paser.asp?isbn=957-819-017-4&ch=201 2002/1/27
微軟電子書苑 第 32 頁,共 50 頁

byte value;
public Digit(byte value) {
if (value < 0 || value > 9) throw new ArgumentException();
this.value = value;
}
public Digit(int value): this((byte) value) {}
public static implicit operator byte(Digit d) {
return d.value;
}
public static explicit operator Digit(byte b) {
return new Digit(b);
}
public static Digit operator+(Digit a, Digit b) {
return new Digit(a.value + b.value);
}
public static Digit operator-(Digit a, Digit b) {
return new Digit(a.value - b.value);
}
public static bool operator= =(Digit a, Digit b) {
return a.value = = b.value;
}
public static bool operator!=(Digit a, Digit b) {
return a.value != b.value;
}
public override bool Equals(object value) {
return this = = (Digit) value;
}
public override int GetHashCode() {
return value.GetHashCode();
}
public override string ToString() {
return value.ToString();
}
}
class Test
{
static void Main() {
Digit a = (Digit) 5;

http://www.e-msbooks.com/Reader/paser.asp?isbn=957-819-017-4&ch=201 2002/1/27
微軟電子書苑 第 33 頁,共 50 頁

Digit b = (Digit) 3;
Digit plus = a + b;
Digit minus = a - b;
bool equals = (a = = b);
Console.WriteLine("{0} + {1} = {2}", a, b, plus);
Console.WriteLine("{0} - {1} = {2}", a, b, minus);
Console.WriteLine("{0} = = {1} = {2}", a, b, equals);
}
}

型別Digit定義了以下的運算子:

z 一個從數字Digit到位元Byte的內含轉型運算子
z 一個從位元Byte到數字Digit的明顯轉型運算子
z 一個增加兩個實值時會得到一個實值的附加運算子
z 一個實值減去一個實值時會得到另一個實值的相減運算子
z 用來比較兩個實值時的等於equality(= =)以及不等於inequality(!=)運算子

索引子(Indexer)

索引子是一個可以使物件如同陣列一樣加上索引的成員,就如同屬性可以產生類似領域的使用般,索引子可
以產生類似矩陣的使用。

就前面曾經提過的類別Stack來說,這個類別為了能夠檢查或警告非必要性且未完成的操作如Push以及Pop
時,希望以矩陣的方式列出。類別Stack會被當成一個連結列表來使用,但仍希望能提供矩陣控制的便利性。

索引子的宣告類似屬性的宣告,兩者最大的不同在於索引子是無名的(在宣告時被命名的名字是this,這麼
來this就算是被索引了)而且索引子中包含許多被索引的參數。索引子的參數會寫在中括弧中,請見下面範
例:

using System;
public class Stack
{
private Node GetNode(int index) {
Node temp = first;
while (index > 0) {
temp = temp.Next;
index--;
}
return temp;
}

http://www.e-msbooks.com/Reader/paser.asp?isbn=957-819-017-4&ch=201 2002/1/27
微軟電子書苑 第 34 頁,共 50 頁

public object this[int index] {


get {
if (!ValidIndex(index))
throw new Exception("Index out of range.");
else
return GetNode(index).Value;
}
set {
if (!ValidIndex(index))
throw new Exception("Index out of range.");
else
GetNode(index).Value = value;
}
}
...
}
class Test
{
static void Main() {
Stack s = new Stack();
s.Push(1);
s.Push(2);
s.Push(3);
s[0] = 33; // Changes the top item from 3 to 33
s[1] = 22; // Changes the middle item from 2 to 22
s[2] = 11; // Changes the bottom item from 1 to 11
}
}

上面範例中顯示了類別stack中的索引子。

執行個體建構子(Instance constructor)

所謂的執行個體建構子是類別中產生一個執行個體時所需要的成員。

範例如下:

using System;
class Point

http://www.e-msbooks.com/Reader/paser.asp?isbn=957-819-017-4&ch=201 2002/1/27
微軟電子書苑 第 35 頁,共 50 頁

{
public double x, y;
public Point() {
this.x = 0;
this.y = 0;
}
public Point(double x, double y) {
this.x = x;
this.y = y;
}
public static double Distance(Point a, Point b) {
double xdiff = a.x - b.x;
double ydiff = a.y - b.y;
return Math.Sqrt(xdiff * xdiff + ydiff * ydiff);
}
public override string ToString() {
return string.Format("({0}, {1})", x, y);
}
}

class Test
{
static void Main() {
Point a = new Point();
Point b = new Point(3, 4);
double d = Point.Distance(a, b);
Console.WriteLine("Distance from {0} to {1} is {2}", a, b, d);
}
}

上面範例表示了一個類別Point提供了兩個public的建構子,其中建構子Point並沒有參數,另一個建構子則有
兩個參數。

如果類別中沒有提供建構子時,CLR會自動提供一個沒有參數的空建構子。

解構子(Destructor)

所謂解構子代表一個類別要被破壞執行個體動作時的成員。解構子不帶有參數,沒有使用權限制,而且不能
被明確的呼叫。對執行個體來說,解構子會在垃圾收集器作用時自動被呼叫。

http://www.e-msbooks.com/Reader/paser.asp?isbn=957-819-017-4&ch=201 2002/1/27
微軟電子書苑 第 36 頁,共 50 頁

請見以下範例:

using System;
class Point
{
public double x, y;
public Point(double x, double y) {
this.x = x;
this.y = y;
}
Point() {
Console.WriteLine("Destructed {0}", this);
}
public override string ToString() {
return string.Format("({0}, {1})", x, y);
}
}

上面範例呈現了一個有解構子的類別class。

靜態建構子(Static constructor)

靜態建構子是個類別產生時所需要呼叫的成員,靜態建構子不帶有參數,沒有使用權限制,而且不能被明確
的呼叫。靜態建構子會在類別被載入時自動的被呼叫。

請見下面範例:

using System.Data;
class Employee
{
private static DataSet ds;

static Employee() {
ds = new DataSet(...);
}
public string Name;
public decimal Salary;
...
}

http://www.e-msbooks.com/Reader/paser.asp?isbn=957-819-017-4&ch=201 2002/1/27
微軟電子書苑 第 37 頁,共 50 頁

上面範例表示了一個類別Employee在產生靜態領域時擁有一個靜態建構子。

繼承(Inheritance)

C#中的類別支援單一繼承,物件型別是所有類別中最基本的類別。

之前曾展示過的範例都是從物件得來的。範例如下:

class A
{
public void F() { Console.WriteLine("A.F"); }
}

上面範例展示了一個從物件中得來的類別A。

請見範例如下:

class B: A
{
public void G() { Console.WriteLine("B.G"); }
}
class Test
{
static void Main() {
B b = new B();
b.F(); // Inherited from A
b.G(); // Introduced in B

A a = b; // Treat a B as an A
a.F();
}
}

上面範例展示了從A中得到類別B,類別B則繼承了A中的方法F,而且在本身的類別中將之命名為G。

方法、屬性以及索引可以是虛擬的,這代表著繼承的類別中它們的執行是可以被忽略的。範例如下:

using System;
class A
{
public virtual void F() { Console.WriteLine("A.F"); }

http://www.e-msbooks.com/Reader/paser.asp?isbn=957-819-017-4&ch=201 2002/1/27
微軟電子書苑 第 38 頁,共 50 頁

}
class B: A
{
public override void F() {
base.F();
Console.WriteLine("B.F");
}
}
class Test

{
static void Main() {
B b = new B();
b.F();
A a = b;
a.F();
}
}

上面範例中表示了一個擁有虛擬方法F的類別A,以及一個覆載F方法的類別B。換句話說,在類別B中包含了
一個覆載的方法稱base.F(),這個方法是直接覆載類別A中的F方法。

類別可以表示成本身是不完整的,而且可以藉由abstract來表示,其本身只是其他類別的基礎類別,這種類
我們稱之為abstract類別。abstract類別中可以指明abstract成員,但繼承它的類別必須真的實作出abstract成
員。請見範例如下:

using System;
abstract class A
{
public abstract F();
}
class B: A
{
public override F() { Console.WriteLine("B.F"); }
}
class Test
{
static void Main() {
B b = new B();
B.F();

http://www.e-msbooks.com/Reader/paser.asp?isbn=957-819-017-4&ch=201 2002/1/27
微軟電子書苑 第 39 頁,共 50 頁

A a = b;
a.F();
}
}

這個範例展示一個abstract類別A,其中包含一個abstract方法F,在非abstract類別B中便必須實作出這個方
法。

結構(Struct)
有太多的例子表示類別和結構多麼類似,如結構可以提供介面(interfaces),而且可以擁有與類別相同的成
員等。然而若要分辨類別以及結構的不同,則有幾個重要的方法:結構是實值型別而不是參考型別,而且結
構並不支援繼承。結構的實值可以用堆疊以及線性儲存,比較細心的程式設計師有時候可以經由審慎的使用
結構來增進效能。

舉例來說,在程式Point中以結構來取代類別,用以節省程式在記憶體中佔用的空間,這在效能上有很大的改
善。下面的程式中產生而且定義了100個點的矩陣,當Point被認為是類別時,這個程式要產生101個不同的
物件,1個給矩陣本身,其他100個分給各元素。

class Point
{
public int x, y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
}
class Test
{
static void Main() {
Point[] points = new Point[100];
for (int i = 0; i < 100; i++)
points[i] = new Point(i, i*i);
}
}

如果Point利用結構來代替的話,請見下面範例:

struct Point
{
public int x, y;

http://www.e-msbooks.com/Reader/paser.asp?isbn=957-819-017-4&ch=201 2002/1/27
微軟電子書苑 第 40 頁,共 50 頁

public Point(int x, int y) {


this.x = x;
this.y = y;
}
}

在上面的程式中則只需要用到配給陣列的一個物件,執行個體Point在陣列中是採用直線分配。這種有效的方
法也可能會被誤用,使用結構來代替類別也有可能會造成程式既龐大又緩慢,因為將整個結構執行個體當做
變數傳遞時,程式會將該結構複製一份傳過去而降低效率。所以善用資料結構並有規則地設計程式將能大幅
改善程式執行效率。

介面(Interface)
一個介面可用來定義一些存取資料的條件;一個引用介面的類別或結構必須符合其定義的條件。介面的成員
可以包含方法、屬性、索引以及事件。

請見下面範例:

interface IExample
{
string this[int index] { get; set; }
event EventHandler E;
void F(int value);
string P { get; set; }
}
public delegate void EventHandler(object sender, EventArgs e);

這個範例表示了包含一個索引、一個事件E、一個方法F以及一個屬性P的介面。

可用介面來支援多重繼承,請見下面範例:

interface IControl
{
void Paint();
}
interface ITextBox: IControl
{
void SetText(string text);
}
interface IListBox: IControl
{

http://www.e-msbooks.com/Reader/paser.asp?isbn=957-819-017-4&ch=201 2002/1/27
微軟電子書苑 第 41 頁,共 50 頁

void SetItems(string[] items);


}
interface IComboBox: ITextBox, IListBox {}

上面範例中,介面IcomboBox同時繼承了ItextBox以及IlistBox。

類別與結構也可以使用多重介面,請見下面範例:

interface IDataBound
{
void Bind(Binder b);
}
public class EditBox: Control, IControl, IDataBound
{
public void Paint() {...}
public void Bind(Binder b) {...}
}

上面範例中類別EditBox是從類別Contro繼承而來,而且同時使用了Icontrol以及IdataBound介面。

在前面的範例當中,在類別EditBox裡,介面Icontrol中的方法Paint以及介面IDataBound中的方法Bind被宣告
成Public成員來使用。C#提供了可選擇的實作方法,實作方法的類別可以避免公開其成員。介面的成員可以
藉由使用符合的名字來實作,舉例來說,類別EditBox中可以藉由提供Icontro.Paint以及IdataBound.Bind方法
來替代用以實作。

public class EditBox: IControl, IDataBound


{
void IControl.Paint() {...}
void IDataBound.Bind(Binder b) {...}
}

以這種方式實作出來的介面稱為明確介面成員,因為每個介面成員在被實作時有被明確的宣告。明確介面成
員只能藉由介面呼叫,舉例來說,EditBox的實施方法Paint只能被介面Icontrol呼叫。

class Test
{
static void Main() {
EditBox editbox = new EditBox();
editbox.Paint(); // error: no such method
IControl control = editbox;
control.Paint(); // calls EditBox's Paint implementation

http://www.e-msbooks.com/Reader/paser.asp?isbn=957-819-017-4&ch=201 2002/1/27
微軟電子書苑 第 42 頁,共 50 頁

}
}

Delegate
Delegates使得其他程式如C++在程式中得以安全使用指標功能,不像原本的指標一樣,Delegates代表的意
義是物件導向、型別安全以及俱安全執行環境的,Delegates是從基本的物件類別庫-System.Delegat得到
參考型別。Delegates執行個體封裝了一個方法-可呼叫的執行個體。對執行個體方法而言,一個可呼叫的執
行個體是由一個執行個體以及該執行個體所包含的方法所組成;對靜態方法而言,一個可呼叫的執行個體則
是由一個類別以及該類別所包含的靜態方法所組成。

一個有用的Delegates屬性可以不用知道其引用的物件是何種型別。任何物件都可以被它引用,最重要的是該
物件的簽章要能符合Delegates的簽章,這點使得Delegates非常適合匿名呼喚,這也是其最有威力的功能。

宣告以及使用Delegates有三個步驟:宣告、執行個體化以及呼叫。可藉由Delegate語法來宣告Delegates,
舉例如下:

delegate void SimpleDelegate();

上面範例宣告一個名叫SimpleDelegate的Delegates,其中沒有任何參數而且是無效的。

請見下面範例:

class Test
{
static void F() {
System.Console.WriteLine("Test.F");
}
static void Main() {
SimpleDelegate d = new SimpleDelegate(F);
d();
}
}

在上面範例中產生了一個SimpleDelegate的執行個體並呼叫它。

將一個Delegates設給一個方法,然後透過Delegates來呼叫這個方法,這樣做並沒有太大的意義,不如直接
呼叫其方法較為簡單,但Delegates在匿名呼叫時就可以顯示出其威力所在,見下面範例:

void MultiCall(SimpleDelegate d, int count) {


for (int i = 0; i < count; i++)

http://www.e-msbooks.com/Reader/paser.asp?isbn=957-819-017-4&ch=201 2002/1/27
微軟電子書苑 第 43 頁,共 50 頁

d();
}
}

上面程式中展示了一個MultiCall方法,並準備重複呼叫宣告為SimpleDelegate的d方法。MultiCall方法不知道
被傳入的d方法的型別、是否可存取、甚至不知道它是靜態或非靜態,不過只要這個方法的簽章符合
SimpleDelegate的簽章就可以了。

列舉(Enum)
一個Enum型別的宣告,可以給相關聯的成員一個象徵性的常數,作為型別名稱。Enum通常用在「多重選
項」的情況。

請見下面範例:

enum Color
{
Red,
Blue,
Green
}
class Shape
{
public void Fill(Color color) {
switch(color) {
case Color.Red:
...
break;
case Color.Blue:
...
break;
case Color.Green:
...
break;
default:
break;
}
}
}

上面範例表示了一個Color的Enum以及其使用方法,這個方法Fill明白地表示shape會被一個選中的顏色填

http://www.e-msbooks.com/Reader/paser.asp?isbn=957-819-017-4&ch=201 2002/1/27
微軟電子書苑 第 44 頁,共 50 頁

滿。

使用Enum是優於使用完整的常數,因為使用Enum讓程式碼擁有更佳的可讀性以及自我驗證。程式碼的自我
驗證可以幫助程式設計師在撰寫程式碼更為敏捷,舉例來說,與其使用數字當作參數型別,對編輯者而言還
是比較喜歡使用Color當作參數型別,因為一看就可以知道是何種顏色的值,但數字就必須查才能知道。

Namespace和assembly
程式的運作除了依據CLR提供的物件類別庫(例如System.Console)之外,還有一些是CLR所沒有提供的部
份。在現實生活中的程式通常由幾個不同的片段所組成,舉例來說,一個公司的應用程式可能分成許多部
份,部份由公司內部人員開發,部份由坊間獨立的軟體販賣商提供。

Namespace及Assembly使這種以元件為基礎的系統得以順利地運作。Namespaces提供了邏輯性的組織系
統,除此之外Namespace可同時扮演程式的內部組織系統以及外部組織系統的角色,這是一個將程式元件呈
現給其他程式的方法。

Assembly是用來作執行個體封裝及部署,它可以扮演型別容器的角色,我們可以封裝的東西包括有型別、被
用來執行型別的可執行程式碼以及作為其他參考的Assembly。

有兩種主要形式的Assembly:應用程式以及函式庫,應用程式有主要進入點而且通常擁有副檔名為.exe的檔
案,函式庫通常沒有主要的進入點,但通常有副檔名為.dll的檔案。

為了示範命名以及Assembly的使用方法,再把先前提過的"hello, world"程式提出來,並將它分成兩部份:一
個提供此訊息的函式庫,另一個展示它的應用程式。

函式庫中包含了一個單獨的類別稱為HelloMessage,範例如下:

// HelloLibrary.cs
namespace Microsoft.CSharp.Introduction
{
public class HelloMessage
{
public string Message {
get {
return "hello, world";
}
}
}
}

這個範例表示了一個類別HelloMessage,其Namespace為Microsoft.Csharp.Introduction。這個類別
HelloMessage提供了名為Message的唯讀屬性。Namespace的命名可以是巢狀,其宣告範例如下:

http://www.e-msbooks.com/Reader/paser.asp?isbn=957-819-017-4&ch=201 2002/1/27
微軟電子書苑 第 45 頁,共 50 頁

namespace Microsoft.CSharp.Introduction
{...}

上面程式表示巢狀命名數個層級的縮寫。

namespace Microsoft
{
namespace CSharp
{
namespace Introduction
{...}
}
}

接下來產生"hello, world"的步驟是利用HelloMessage類別撰寫一核心應用程式。在這裡可以使用完整的名稱
如Microsoft.Csharp.Itroduction.Hellomessage,但這個名字卻太長而且不精簡,較好的方法是使用
namespace導向技術,這樣就可以使用其中包含的所有型別,範例如下:

// HelloApp.cs
using Microsoft.CSharp.Introduction;
class HelloApp
{
static void Main() {
HelloMessage m = new HelloMessage();
System.Console.WriteLine(m.Message);
}
}

這個範例展示使用namespace技術來引用Microsoft.Csharp.Introduction,此時HelloMessage就是代表
Microsoft.Csharp.Introduction的意思。

C#也提供支援別名(alias)的功能,透過using alias directive可以定義alias型別。這種作法可以減少兩個函


式庫名稱衝突,或者在大量namespace中引用少數型別時特別方便,其範例如下:

using MessageSource = Microsoft.CSharp.Introduction.HelloMessage;

上面範例表示了使用alias去定義MessageSource當作HelloMessage類別的代表。

最後我們撰寫好的程式碼可以被編譯入包含類別HelloMessage的函式庫以及包含類別HelloApp的應用程式。
詳細編譯的步驟可能會由於使用的編譯器或工具的不同而有所差異,使用Visual Studio 7.0所提供的命令列編
譯器,應使用下面的指令:

http://www.e-msbooks.com/Reader/paser.asp?isbn=957-819-017-4&ch=201 2002/1/27
微軟電子書苑 第 46 頁,共 50 頁

csc /target:libra HelloLibrary.cs

上面這行是產生應用程式HelloLibrary.dll的指令。

sc /target: HelloLibrary.dll HelloApp.cs

上面這行產生應用程式HelloApp.exe的指令。

Versioning
Versioning是發展一個相容方法元件的過程。元件的新版本程式碼應該相容於先前版本的程式碼,即先前的
版本經過重新編譯過後要能與新版本一樣工作,相對的,新版本的元件能否順利的運作取決於是否能和舊的
版本相容。

大部分的程式言語完全不支援二進位,有一些部分支援。事實上,一些程式語言因為本身的缺陷,造成它在
客戶端執行時可能會破壞到別的程式。

舉一個例子,試想有一個基本的類別叫Base,第一個版本時,Base並不包含F的程序,此時有一個叫
Derived的元件,由Base衍生出來,而開始包含有F。這個Base類別與Derived類別一起發行出去給顧客,部
署在眾多的客戶端與伺服端。

// Author A
namespace A
{
public class Base // version 1
{
}
}
// Author B
namespace B
{
class Derived: A.Base
{
public virtual void F() {
System.Console.WriteLine("Derived.F");
}
}
}

程式到目前為止一切順利,但這時開始產生了一些版本上的困擾,Base的作者製造了一個新的版本,加入了
F程序。

http://www.e-msbooks.com/Reader/paser.asp?isbn=957-819-017-4&ch=201 2002/1/27
微軟電子書苑 第 47 頁,共 50 頁

// Author A
namespace A
{
public class Base // version 2
{
public virtual void F() { // added in version 2
System.Console.WriteLine("Base.F");
}
}
}

這個新版本必須同時和最初一開始的版本相容(想要在不影響最初的base類別的情況下加入一個新方法幾乎
是不太可能),很不幸地,Base中加入F讓Derived的F方法變得不知道要如何繼承。Derived能夠蓋過Base
的F嗎?這看來不太可能,因為當Derived被編譯時,Base還沒有包含F。再者,若Derived的F真的蓋過Base
的F,它必然有些東西是當初在設計Derived時沒有考量到的。所以Derived是不能這樣覆載Base的。

C#語言中藉著讓程式設計師清楚的陳述他們的需求,以解決這種版本上的問題。在最初的程式碼範例中,程
式是非常明確的,Base中沒有F。明顯地,Derived的F是一項新的程序,而不是蓋過Base所產生的程序。

// Author A
namespace A
{
public class Base
{
}
}
// Author B
namespace B
{
class Derived: A.Base
{
public virtual void F() {
System.Console.WriteLine("Derived.F");
}
}
}

如果Base加了F後變成一個新的版本,Derived的binary版本一樣是很清楚的-Derived的F在語意上是沒有相
關的,所以不會被覆載。

然而,當Derived重新編譯時,其意義就變得混淆了,Derived的作者可能要他的F去覆載掉Base的F或者隱藏

http://www.e-msbooks.com/Reader/paser.asp?isbn=957-819-017-4&ch=201 2002/1/27
微軟電子書苑 第 48 頁,共 50 頁

它,但是編譯器會產生警告,並且會用Derived的F隱藏Base的F。這樣一來將警告Derived的作者,使其知道
Base中有方法F的存在。

如果Derived的F確實和Base的F沒有相關聯,Derived的作者可以不用理會這種警告,並在宣告F時使用新的
關鍵字。

// Author A
namespace A
{
public class Base // version 2
{
public virtual void F() { // added in version 2
System.Console.WriteLine("Base.F");
}
}
}
// Author B
namespace B
{
class Derived: A.Base // version 2a: new
{
new public virtual void F() {
System.Console.WriteLine("Derived.F");
}
}
}

反方面來說,Deriver的作者可能察看未來的需要性,而且決定Deriver的F應該要取代Base的F,這樣的話可
以經由使用特定的關鍵字override來表達,如下面範例所示:

// Author A
namespace A
{
public class Base // version 2
{
public virtual void F() { // added in version 2
System.Console.WriteLine("Base.F");
}
}
}

http://www.e-msbooks.com/Reader/paser.asp?isbn=957-819-017-4&ch=201 2002/1/27
微軟電子書苑 第 49 頁,共 50 頁

// Author B
namespace B
{
class Derived: A.Base // version 2b: override
{
public override void F() {
base.F();
System.Console.WriteLine("Derived.F");
}
}
}

Derived的作者可能有其他意見,例如更改F的名字,如此可完全避開名字的衝突,雖然這樣的改變可能使
Derived破壞原始檔以及雙位元的完整性,但這種完整性最重要是取決於程式本身。如果Derived並沒有被其
他程式所使用,那麼更改F的名字像是個好主意,而且會增加程式的易讀性,因為程式中不會被其他的F所困
擾。

性質(Attribute)
C#是一種程序性的程式語言,它像所有其他的程序性程序語言一樣,擁有許多宣告性的元素。例如,在類別
中使用程序是使用public、protected、internal、protected internal或private這些來修飾的,因為C#語言遵循
著這些性質,並歸納這些能力,所以程式設計師能夠創造新種類的宣告資料,並能在許多程式的執行個體及
擷取宣告資訊時及時地詳細說明這些宣告資料。程式也能藉著定義使用的性質來說明這些額外的宣告資料。

例如,HelpAttribute性質可能放在程式的類別或程序中定義出架構,如此能夠讓程式設計師提出程式元素的
位置,並有記錄可參考引用它們。以下為範例:

using System;
[AttributeUsage(AttributeTargets.All)]
public class HelpAttribute: Attribute
{
public HelpAttribute(string url) {
this.url = url;
}
public string Topic = null;
private string url;
public string Url {
get { return url; }
}
}

http://www.e-msbooks.com/Reader/paser.asp?isbn=957-819-017-4&ch=201 2002/1/27
微軟電子書苑 第 50 頁,共 50 頁

在上面的例子中定義了一個名為HelpAttribute的性質類別,或簡稱為Help,包含有位址參數(string url)以
及名稱參數(string Topic)。位址參數是供Public型態的性質類別在建構子中所定義,而名稱參數是性質類
別中可讀寫的性質。

範例如下:

[Help("http://www.mycompany.com/.../Class1.htm")]
public class Class1
{
[Help("http://www.mycompany.com/.../Class1.htm", Topic = "F")]
public void F() {}
}

範例中顯示了幾個性質的使用。

程式元素中的性質資料可能在執行的同時,藉著回應的方式擷取出來。範例如下:

using System;
class Test
{
static void Main() {
Type type = typeof(Class1);
object[] arr = type.GetCustomAttributes(typeof(HelpAttribute));
if (arr.Length = = 0)
Console.WriteLine("Class1 has no Help attribute.");
else {
HelpAttribute ha = (HelpAttribute) arr[0];
Console.WriteLine("Url = {0}, Topic = {1}", ha.Url, ha.T
}
}
}

在上面範例中,Class1擁有一個Help性質,同時也顯示當性質呈現時,Topic和Url二者值的關係。

Copyright1997-2001 SoftChina Corporation. All rights reserved.

http://www.e-msbooks.com/Reader/paser.asp?isbn=957-819-017-4&ch=201 2002/1/27

You might also like