Object & Class

Download Report

Transcript Object & Class

Visual C# 2010 程式設計經典
第6章
物件與類別
6.1 前言
在物件導向程式設計觀念未成熟之前,軟體開發人員
大都使用程序導向(Procedure Oriented)的觀念來設
計程式。

一個好的程序導向程式,結構化程式設計是必要的基
本要件,結構化程式的優點就是可讀性高且易偵錯與
維護。

至於結構化程式除了必須使用結構化的程式語言來設
計外,應:
1. 由上而下(Top-Down Design)方式來分析問題。
2. 採模組化(Modulize)程式設計。
3. 有足夠流程控制敘述如選擇結構、重複結構以供不同
流程變換。





程序導向程式設計主要是用來解決程式設計的方法。
它透過主程式整合為一個完整問題,每個問題可細分
至電腦所能處理並撰寫成程序或函式。
一個撰寫完成的程序或函式雖可重複使用但不具擴充
性。
譬如:較大的問題當然需要較複雜的程式,雖以模組
化來處理,也會面臨模組間資料結構的多樣化與多量
化、程式整合時的測試、維護和擴充性都會產生困擾,
導致對程序或函式所傳入的資料結構無控制能力。


物件導向(Object Oriented)程式設計是另一種用來解決
程式設計的方法。
它按照人類真實的想法來分析和解決問題,使得物件
與真實世界有一個直接的關係,不需經過任何轉換就
可以讓我們更易於了解。
6.2 物件與類別
6.2.1 什麼是物件

在真實世界中,物件就是東西,物件就是物體。構成
件的要素有如下:

一. 物件具有屬性
二. 物件具有方法
三. 物件要有訊息與事件
四. 物件要能被識別



6.2.2 什麼是類別




類別(Class)是用來對物件做分門別類,以數學角度來
看類別就像是一個集合,所以類別是一群具有相同性
質物件的集合。
類別是一種設計的方法,相當於一個模板(template),
物件就是根據類別的設計方法(模板)所製作出來的成品。
類別就是建構某些相似物件的藍圖,物件可視為依類
別的描述所建構出來的一個類別的物件實體
(Instances)。
類別只是用來描述這些類似物件的屬性、方法,類別
本身並不是實際的物件。



例如:腳踏車、越野車、三輪車、轎車、公車等都是
實際存在的物件,都屬於車子這個類別,他們都有屬
性(輪子、方向盤、煞車裝置… )和執行方法(會跑、會
停、會轉彎…)。
但是 “車子” 這個類別只是用來描述腳踏車、越野車、
三輪車、轎車、公車這四種車子統稱,定義 “車子”
這種類別含有哪些屬性和方法。
但是 "車子" 這個類別卻無法執行方法,因為它只是一
種描述而已,實際上是不存在的,只有由
"車子" 類別所衍生出來的物件(腳踏車、越野車、三輪
車、轎車、公車等)才可以真正執行方法。







從程式設計的觀點來看,類別只是一種抽象的資料型
別,而物件則是屬於該種資料型別的實體變數。
例如:在C# 的int可以看做是整數類別,卻無法直接用
int類別來做加減運算(方法):
int A;
int B;
A = 10;
B = A + 5;
int = 20;
// 宣告變數A是一個整數
// 宣告變數B是一個整數
// 正確
// 正確
// 錯誤
6.3 物件導向程式設計的特性

物件導向程式設計的特性包含:
1.
2.
3.
4.
5.
抽象化 (Abstraction)
封裝 (Encapsulation)
繼承 (Inheritance)
多形 (Polymorphism)
動態繫結 (Dynamic binding)
6.3.1 抽象化




抽象化(Abstraction)顧名思義,若以它的動詞Abstract
(萃取) 來思考或許會更加清楚。
一般的高階語言都將變數抽象化,利用資料抽象化可
使資料隱藏,抽象化只注重物件和外界溝通的行為,
而與資料內部執行細節沒有關係。
例如:單價以price當作變數名稱,而不以A、B這種無
意義的方式來進行資料處理。
在物件導向程式設計中,更將抽象化擴展到物件上,
程式設計者可以直覺方式針對Car1 (汽車) 物件,以
Car1.Weight代表汽車的重量、Car1.Start()啟動汽車方
法,而不用GetWeight(Car1)函式來取得汽車的重量、
用Start(Car1)函式來啟動汽車。
[程式分析]





由於本例會使用GDI+繪圖類別在表單上進行繪圖,因此先
介紹基本的繪圖技巧:
在表單上建立畫布物件g及建立畫筆物件p,且畫筆物件可
繪製紅色線段:
Graphics g ;
Pen p = new Pen(Color.Red);
g = this.CreateGraphics();
將畫布物件清成白色畫布:
g.Clear(Color.White);
畫布物件上使用紅色畫筆在兩點座標(50,50)、(200,50)繪製
一條水平線段:
g.DrawLine(p, 50, 50, 200, 50) ;
畫布物件上使用紅色畫筆在座標(0,0)、(90,90) 所構成四方
框內繪製內切橢圓:
g.DrawEllipse(p, 0, 0, 90, 90);

畫布物件上使用紅色畫筆在座標(150,0)、(90,90) 所構成
四方框內繪製一條00~2700的圓弧:
g.DrawArc(p, 150, 0, 90, 90, 0, 270);
;

本例程式中的Shape.Circle就代表0、Shape.Rectangle代
表1、Shape.Arc代表2,這樣不是變得清楚多了嗎?enum
(列舉) 其實是一個資料型別,通常是用來將一組無意義的
資料用有意義的方式來表現出來,例如:
enum WeekDays : int
{
Monday = 1,
Tuesday = 2,
Wednesday = 3,
Thursday = 4,
Friday = 5,
Saturday = 6,
Sunday = 7
};
//
//
//
//
//
//
//
星期一
星期二
星期三
星期四
星期五
星期六
星期日

抽象化(Abstraction)即是在程式語言中就是使用
ADT將眾多資料的一般化規則Abstract (萃取、抽)
出來,以達到程式語言抽象化的特性。
6.3.2 封裝



每個人都有自己的隱私,若喪失了隱私,就讓別人一覽無
遺,毫無祕密可言。
資料的封裝就有如人類的隱私。物件也是一樣,或多或少
都有屬於物件內部的私有部份 (屬性、方法…),而這些部
份必須是外界無法直接存取,如此才能保有物件的完整性。
例如:對於一般人來說,XBox360遊戲機 (物件) 只是學習
如何透過面板來操控 (方法)遊戲,對於XBox360內部的元
件構造 (實體) 並不需要去了解,若一時好奇打開遊戲機盒
自行拆解 (破壞封裝),那麼有可能弄壞遊戲機,而無法恢
復原來的功能。因此物件必須將私有的部份封裝在物件的
內部,而使用者只能藉由物件所提供的方法、屬性來操控
物件,以保持物件的完整性,這就是封裝的特性。



至於物件導向程式語言,將資料結構和用來操作該資
料結構的所有方法都封裝在物件的類別定義中。
外界無法直接存取該物件內部的資料結構,僅能透過
物件開放的存取介面來進行存取,因此可以保護物件
的完整性。
譬如使用提款機提款,提款人只能經由提款機所提供
的螢幕與按鈕,經過密碼確認後便可進行提款作業,
無法自行直接由提款機內部存取現金,如此便可以確
保提款程序的正確性。
6.3.3 繼承




在真實世界中,有許多物件的特徵和行為很類似,而
這些性質相似的物件往往都是經由繼承而來 。
父類別中的資料或方法在子類別中的物件就可繼承使
用,子類別往下延伸的次子類別物件也可繼承使用。
由於物件具有繼承的特性,使得物件導向程式設計具
可再用(Reused)和擴充性。
例如一部新型汽車可以繼承柴油汽車的大部份特性:
方向盤、輪子、汽車座椅…,而改用噴射引擎、ABS、
四輪傳動系統…,就可以造出一部功能更強、跑得更
快的新車了。
所以,物件有了繼承的特性,就可以不用完全重新製
作一個新的物件。

例如下圖的RaceCar (賽車) 物件繼承自Car (汽車) 物
件,並新增了Turbo方法:
6.3.4 多型



多形(Polymorphism)又稱「同名異式」。
所謂「多形」就是物件可使用相同功能(方法)介面,來
操作不同類型的物件,而產生不同行為的一種機制,
簡言之就是「一個介面,多個方法」。
物件有了多形的特性,可以簡化很多物件處理的過程。
也就是說物件允許它的方法名稱相同,卻依參數個數
或參數資料型別的不同而產生多個方法。程式執行時
會選擇合適的方法來執行。

例如下圖中,RaceCar (賽車) 和PoliceCar (警車) 都繼承自
Car物件類別:

假設A是RaceCar物件、B是PoliceCar物件,C是Car
物件,若執行下列敘述後,則C.Accelerate(加速)所執
行的是哪個物件的Accelerate方法呢?這就要看所繫結
的物件為何了,譬如:
C = A;
// 表示C指向物件A
C.Accelerate(); // 執行RaceCar物件的Accelerate方法

反之
C = B;
// 表示C指向物件B
C.Accelerate(); // 執行PolicCar物件的Accelerate方法
6.3.5 動態繫結

一般在呼叫物件方法,有兩種與物件的繫結方式,一
種是靜態繫結(Static binding)另一種方式是動態繫結
(Dynamic binding)。
一. 靜態繫結


指的是編譯器在程式編譯階段,就將物件與方法繫結
在一起。
例如:A.DoIt()直接編譯成機器碼直接呼叫 A物件的
DoIt()方法。
二. 動態繫結


指的是編譯器在程式編譯的階段並不將物件與方法繫
結在一起,而是將物件的方法函式的位址建立成一個
虛擬表格 (Virtual table),在執行階段時,再由虛擬表
格中判斷該呼叫哪一個物件的方法函式,例如執行
A.DoIt()時,先判斷A.DoIt()是哪一個物件的DoIt()方法,
再由虛擬表格中找到該方法函式的位址,進行函式呼
叫,因此可以做到物件多型 (Polymorphism)。
不同程式語言在處理動態繫結所使用的Virtual table技
術不見得相同 (例如函式命名方式),就連同樣是C++
語言,在不同編譯器 (Visual C++、Borland C++
Builder …) 下就有不同的內部處理方式,不過這並不
會影響程式設計的方式,因為低階的部份已經完全由
編譯器處理掉了。
6.4 物件與類別的建立
6.4.1 如何建立類別


在C# 中使用class{…}來定義一個類別,要特別注意的是這
個類別的定義哪裡都可以放,就是不能放在方法(函式)中
(當然包含事件),也不能放在namespace{…}外,也就是類
別定義一定是全域性的宣告。
如下範例使用class定義一個空白的類別MyFirstClass,並
且使用這個類別來建立物件名稱為A的物件。執行結果如下
圖:
6.4.2 命名空間



命名空間(Namespace)可以有效地將眾多物件根據它
的功用有效地分類,也可以避免不同廠商採用相同名
稱的困擾。
假設我們要在同一個C# 程式檔(*.cs)中宣告兩個名稱
一模一樣的類別,C# 編譯器一定會因為名稱重覆而出
現錯誤,此時可利用命名空間來解決這個問題。
例如IBM和Apple兩家國際電腦公司都生產Notebook
(筆記型電腦),這時候就可以用namespace{ … } 來定
義IBM和Apple兩個不同的命名空間,然後在各自的命
名空間中定義一個Notebook類別,如此程式中便可使
用IBM.Notebook和Apple.Notebook來區分出是屬於哪
家筆電的Notebook類別名稱。



IBM.NoteBook 表示IBM命名空間裡面的那個
Notebook類別,而Apple. Notebook就表示Apple命名
空間裡面的那個Notebook類別了,這樣就可以避免宣
告出AppleNotebook和IBMNotebook這種太長而且不
具分類的類別名稱了。
這裡有一點要特別注意的,那就是namespace{ … }敘
述只能放在檔案層級,也就是說不可以放在方法(函式)
或class宣告中,不過namespace中還是可以有其它
“子命名空間”。
譬如:延續上例IBM公司又細分成台灣IBM和日本IBM,
如此要區別這兩家分公司的Notebook類別名稱,就需
要如下面敘述使用巢狀的namespace來定義 "子命名
空間":
6.4.3 如何建立屬性

建立屬性的方式大致上可以分成下面兩種方法:
1.
2.
直接在類別中宣告public變數。
使用get及set存取子。
一. 如何使用public變數建立物件屬性




下面範例直接在類別中宣告public變數。
譬如定義類別名稱為Car的類別,此類別內含一個整數資料
型別的變數Speed(用來表示車子的速度),在變數加上
public變成公用變數,這樣Car類別就有一個屬性了。
此種做法是不是和使用struct定義結構欄位變數差不多呢?
類別是由結構的觀念而來,也就是說類別除了欄位(或稱屬
性)外還包括方法(或稱函式)。
二. public變數的潛在缺點


直接在class中宣告public變數雖然是建立屬性最快速
的方式,但是對於這類型屬性的存取並無法做任何的
額外控制。
例如我們要為Car類別加上一個Speed (速度) 屬性,但
是希望Speed屬性的值能夠限制在 0 到 200 之間,若
採public變數來做會發現,Speed屬性的值可以隨便設,
設成負數都可以,是其缺點。
三. 如何使用方法或存取子建立屬性


若希望對於屬性的存取都能做一些屬性值的範圍控制,使
其具有物件封裝的特性,可以將速度屬性由public改成
private變數,以_speed代替Speed公用變數,用來存放
Speed屬性的值,由於_speed是private的,因此在Car上不
可以使用_speed做為屬性。
下面範例改以定義一個GetSpeed()方法用來取得_speed屬
性值,另一個SetSpeed()方法用來設定_speed屬性值,限
制速度是在0 ~ 200之間來解決上例發生的問題。


使用這種方式有一個小缺點,那就是在設定屬性時必
須用呼叫方法加參數的方式:Benz.SetSpeed(500);
而不能直接使用指定屬性值的方式:
Benz.Speed = 500;
因此並不是很方便,不過以往在C++ 中就是使用這種
方式來設定屬性值的。



C# 提供get及set存取子敘述來定義屬性。
您可以使用get存取子來取得物件的屬性值和使用set存取子
來設定物件的屬性值。
譬如:使用get取得_speed屬性值;set設定_speed屬性值
介於0~200間,若速度小於0,則_speed設為0;若速度大
於200,則_speed設為200。其程式寫法如下:
public int Speed
{
get
{
return _speed;
}
set
{
if (value < 0) value = 0; // 速度不得低於 0
if (value > 200) value = 200;
// 速度不得高於 200
_speed = value ;
// 設定屬性值
}
}
四. 如何建立唯讀屬性




要建立一個唯讀(ReadOnly)屬性 (也就是不能修改的屬
性)可使用下面方式:
第一種方式就是在上述Speed屬性的set {…} 區段中不
要加入任何程式碼,或者只加入顯示錯誤訊息的程式,
這樣的確可以做出唯讀的屬性,但是在程式編譯階段,
C# 編譯器並不會對於Benz.Angle = 180; 之類的設定
屬性敘述,出現任何錯誤或警告訊息,因為語法並沒
有任何錯誤
第二種方式標準的作法是在屬性的定義中只能出現get
{…} 區段,絕對不能加入set {…} 區段。
如下範例程式碼:
五. 如何建立唯寫屬性


同理,如果要做出唯寫(Write)屬性 (只能修改、不能讀
取),做法就是在屬性設定區段裡面只出現set {…} ,
絕對不能加入get {…} 區段。
如下範例:
六. 自動屬性實作




在類別內使用get及set存取子來定義屬性最大的優點即是可
以隱藏實作以及驗證程式碼,以達物件導向資料封裝;另
外使用get及set存取子來定義類別屬性,該屬性即可與控制
項屬性進行資料繫結。
若使用public公用變數當做類別屬性,即無法達到資料封裝,
且該屬性也不能和控制項屬性進行資料繫結,因此建議將
public公用變數改使用public公用屬性來表示。
Visual C# 2005之前的版本若是將所有public公用變數全部
改成使用屬性來表示,那將會是大工程。
而且像學生姓名這種單純是字串型別的變數並不需要進行
資料封裝,若改以屬性表示,就要先建立私有變數來存放
學生姓名內容,再透過get存取子來取得學生姓名屬性,最
後透過set存取子來設定學生姓名屬性,如此是多麼麻煩的
一件事。
譬如下面簡例在Student學生類別內宣告私有變數 _StuID及
_StuName用來存放學生的學號及姓名,再透過get及set存取
子來定義公用的StuID編號及StuName姓名屬性。
class Student // 定義學生類別
{
private string _StuID, _StuName;
public string StuID
{
get{return _StuID;}
set{_StuID = value; }
}
public string StuName
{
get{return _StuName;}
set{_StuName = value; }
}
……
}


由Visual C# 2008版本開始提供「自動屬性實作」讓屬性的
定義更為明確,且使用get及set存取子來定義屬性即不需要
重複宣告存放屬性的私有變數,這些存放屬性的私有變數
會由編譯器自動建立。
因此上面簡例,可修改如下寫法,結果發現程式碼變的非
常精簡。
class Student // 定義學生類別
{
public string StuID{ get; set; }
public string StuName{ get; set;}
……
}
6.4.4 如何建立方法
一. 如何建立方法


物件的方法 (也有人稱之為成員函式 Member Function)
其實就是定義在類別中的函式,而方法的參數也不過
就是函式的參數罷了。
例如我們要替Car類別定義一個Move方法,其範例程
式碼如下:


沒錯,只要定義一個包含兩個參數的公開Move方法就
可以了,非常的容易,不過如果我們要配合之前所定
義的一個Speed屬性,另外定義一個加速的方法
Accelerate,就會出現一些缺點,由於Speed屬性的範
圍在0 ~ 200之間。
因此Speed屬性和Accelerate方法中就必須各做一次範
圍的檢查與設定,例如下面範例:
二. 如何呼叫自身類別的屬性與方法

因此我們可以將上述範例Accelerate方法中的程式碼改
成下面這樣子:
// ************CallingPropertyFunctionInMethod-2.sln**
public void Accelerate()
{
this.Speed ++;
}
// ***********************************************************


其中this表示物件自己本身,也就是使用物件本身的
Speed屬性,會自動先呼叫Speed屬性的get存取子取
得目前的值,加上1之後再自動呼叫Speed屬性的set
存取子設定新屬性值,由於在set存取子中本來就設計
了處理屬性範圍的程式碼,因此將來呼叫Accelerate方
法來加速時,速度最高只能到200,不會超出範圍。
當然如果要在物件中呼叫自己的方法也可以,例如要
呼叫物件自己的Move方法,就用this.Move(100, 200);
敘述,而參考物件內部的變數也是一樣,例如
this._speed。

那麼不加this可不可以?其實在這個範例是可以省略this,
直接用Speed++;即可,不過萬一在方法(函式)中有另一個
區域變數名稱也叫做Speed,那就一定要加上this,否則指
的是區域變數Speed,而不是物件屬性Speed,例如:
// *******************************************************
public void Accelerate()
{
int Speed;
// 定義區域變數Speed
Speed ++; // 指的是區域變數 Speed
this.Speed++; // 指的是物件 Speed 屬性
}
// *******************************************************

因此只要是要參考到物件自己的方法、屬性或變數,最好
在前面加上this,比較妥當。
三. 方法多載

假設我們希望Car類別中的Accelerate方法能夠有多種
加速的方式,例如:
Accelerate();
Accelerate(50);
Accelerate("STOP");

// 速度加 1
// 速度加 50
// 停車
這時候由於上述兩種Accelerate方法的參數個數與資料
型別並不一樣,因此就可以使用多載(Overloading)來
達成:
四. 建構式與解構式



類別定義中有兩種很特別的方法,分別是建構式
(Constructor) 與解構式 (Destructor)。
建構式是在建立物件時用來做物件初始化工作,例如:
開啟資料檔案、配置記憶體…。
建構式中可以用來做一些物件的初始化動作,而當物
件消滅時,就會執行物件的解構式,在解構式中可以
做一些物件結束動作 (例如:關閉資料檔案、釋放所配
置的記憶體…)。

關於建構式與解構式我們整理下面七點:
1. 若類別中未定義建構式,會自動提供一個不做任何事的預設建構
式(default constructor)。
2. 建構式的名稱必須與類別名稱同名。
3. 建構式也可以多載,其做法和多載方法一樣,是使用不同的引數
串列的個數和引數串列的資料型別來加以區隔建構式。
4. 建構式和解構式沒有傳回型別,即使是void也不需要。
5. 解構式的名稱必須和類別一樣,且解構式名稱之前要加上「~」
符號。
6. 解構式是無接受參數的方法,且只能有一個,因此解構式無法多
載。
7. 解構式無法直接呼叫,只有在物件被破壞時才會執行。
使用「ConstructorDemo-1.sln」這個範例來說明建構式的使用方式,
此範例的Student類別內定義了三個建構式用來做Weight體重和
Height身高的設定。下圖是範例的執行結果:



定義類別時若沒有撰寫建構式時,C# 會自動產生一個預設建構
式(Default Constructor),這表示物件產生時,所有的資料成員都
不必進行初始化的動作。
若有定義建構式時,預設建構式會自動消失,若想要在程式中使
用Student Fred = new Student(); 會產生下圖錯誤。
例如把本例第6-10行程式碼刪除,或者可參考「Constructor
Demo-2.sln」,然後重新編譯一次時,則會出現如下圖的錯誤:


解決這個問題的方法就是在Student類別中加上下列程式碼,
也就是說在public Student(){ … } 建構式內不要撰寫任何程
式碼,如此一來表示Student類別中也有預設建構式,這樣
子就不會出錯了。
您可參考「ConstructorDemo-3.sln」的程式碼,下圖是範
例的執行結果:
public Student()
{
}



以下範例來解說建構式與解構式。
在Car類別加上兩個建構式,用來設定速度的初始值。
並且加上Car 類別的解構式用來顯示 “車子物件消滅
了…” 一行訊息,其中一個建構式不需傳入參數;另一
個建構式必須傳入一個整數。
範例程式碼如下:


執行的結果如上圖,其中BMW物件是在DoSomething()方法中宣
告的區域物件,而Benz物件則是在Main() 方法中宣告的。
結果是不是令人大失所望,程式似乎根本就沒有執行到物件解構
式~Car,為什麼會這樣子呢。



原來解構式在物件消滅時是一定會執行的,但是你無
法預期它什麼時候執行,唯一能確定的是,.NET
Framework一定會在之後的某一個時間執行它。
為了程式執行的效率, .NET Framework中的Garbage
Collection Process會在記憶體不足或程式結束時才會
回收物件的記憶體,Benz = null; 敘述只是將Benz這
個物件的參考(reference)釋放掉,並不見得會回收
Benz原本所指的物件。
所以當你結束執行本範例時,如下圖主控台視窗會顯
示兩行「車子物件消滅了…」的訊息,接著再關掉主
控台視窗。
如果想要強制回收物件的記憶體,只要使用 GC.Collect(); 敘述就可以
了,您可參考範例Constructor-Destructor-2.sln,如下程式只要在適當
的地方使用GC.Collect(); 敘述,就可以強制執行記憶體回收機制。
// ************ Constructor-Destructor-2.sln********
static void Main(string[] args)
{
DoSomething();
Console.WriteLine("宣告 Benz 物件 ..");
Car Benz = new Car();
Console.WriteLine("Benz 物件宣告完成 ..");
Console.WriteLine("準備執行 Benz = null ...");
Benz = null;
Console.WriteLine("Benz = null 執行完成 !!");
GC.Collect(); // 強制執行記憶體回收機制
Console.Read();
}
// ****************************************************
6.4.5 如何建立事件




事件其實有點類似方法,唯一的差別是方法中我們必須事
先定義好所需執行的程式碼,而事件則是在宣告物件時才
由程式設計師針對自己的需要來撰寫事件。
例如button1物件原本就有Move方法,因為Move方法是在
Button類別中早就事先定義好的,而button1_Click事件則
是必須自行在類別之外 (不是在類別中定義) 撰寫相關的程
式碼,也就是說在類別中僅僅定義事件的名稱與參數,至
於事件 (事件函式)則是在物件使用時才加以定義的。
延用之前的Car類別,假設我們希望當物件的Speed屬性值
超過200時,物件能夠透過事件通知我們,並且將目前的速
度當成事件的參數,以便我們在事件中可以知道目前的速
度。
現在以範例Event-1.sln介紹如何定義事件的步驟:
一. 建立delegate委派型別




delegate (委派)型別擁有類似C++中的函式指標,它可
以指向方法的參考指標,也就是說透過 delegate型別
可以呼叫物件(執行個體)的方法,以傳回特定的資訊。
在C# 中delegate型別最常應用在事件處理上。
本例使用下面敘述定義事件delegate型別,其名稱為
DangerEvent,事件傳入的參數為一個int整數型別。
(參考第3行程式)
delegate void EventDanger(int vSpeed);
二. 建立event敘述宣告事件


接著可以在類別中宣告一個事件如下:
(參考第9行程式)
public event DangerEvent Danger;
這個事件的名稱叫做Danger,屬於DangerEvent
delegate型別,而且有一個整數參數vSpeed。
三. 如何觸動事件



那麼當物件發覺Speed屬性值超過200時,要怎樣才能
觸動這個事件呢?你只要在Speed屬性set {…} 區段
(設定屬性值時會執行到) 中,直接呼叫事件即可。
本例使用下面敘述,若Danger有指向一個方法的參考
(Danger != null),則馬上呼叫事件。(參考第21行程式)
if (Danger!=null) Danger(value);
也就是將新的Speed屬性設定值value當做參數來呼叫
Danger事件,有點像在呼叫一般的方法(函式)一樣。
四. 如何定義事件

回憶一下之前幾章所介紹的控制項事件,當我們在
button1按鈕上按一下時,不是會觸動Click事件嗎?於
是我們就必須將所要執行的程式寫在button1_Click事
件處理函式中,在自定的物件中也是差不多的。

在Program類別中設計了一個靜態(static)的TooFast方
法,用來當做事件處理函式,既然是事件處理函式,
當然參數就要和Car類別中的Danger事件定義一樣才
可以,也就是都有一個vSpeed的整數參數,用來表示
目前的速度。(參考第30~33行程式)
五. 指定物件發生事件所要處理的方法

當Speed屬性設定超過200時,物件會呼叫Danger事
件,然後我們的事件處理方法(函式)是TooFast …,好
像沒這麼順利不是嗎?

類別中所執行的是Danger事件,跟我們後來定義的
TooFast方法根本一點干係都沒有,因此我們還要清楚
定義「呼叫Danger事件其實就是呼叫TooFast方法」
這項重要的事,在宣告出Benz這個Car物件後必須再
使用 new 關鍵字來建立屬於DangerEvent delegate型
別的實體,然後將Benz物件中的Danger事件對應到
TooFast方法:(第38行)
Benz.Danger += new DangerEvent(TooFast);

當我們將Benz的Speed屬性設定成300,Benz物件的
Speed屬性就會執行Danger事件,而由於定義了Benz
物件的Danger事件就是TooFast事件方法(事件處理函
式),於是就會執行TooFast事件方法(事件處理函式)了。

範例完整程式碼如下: