一個事件是一個使對象或類可以提供公告的成員。用戶可以通過提供事件句柄來為事件添加可執行代碼。事件使用事件聲明來聲明:
一個事件聲明既可以是一個事件域聲明也可以是事件屬性聲明。在每種情況中,聲明都可以由屬性集合, new 修飾符, 四個訪問修飾符的有效組合 和一個靜態修飾符組成。
一個事件聲明的類型必須是一個代表類型, 而那個代表類型必須至少同事件本身一樣可訪問。
一個事件域聲明與一個聲明了一個或多個代表類型域的域聲明相應。在一個事件域聲明中不允許有readonly 修飾符。
一個事件屬性聲明與聲明了一個代表類型屬性的屬性聲明相應。除了同時包含get訪問符和set訪問符的事件屬性聲明,成員名稱和訪問符聲明對於那些屬性聲明來說都是相同的,並且不允許包含virtual、 override和abstract 修飾符。
在包含一個事件成員聲明的類或結構的程序文字中,事件成員與代表類型的私有域或屬性相關,而這個成員可以用於任何允許使用域或屬性的上下文中。
如果一個類或結構的程序文字外面包含一個事件成員聲明,這個事件成員就只能被作為+= 和 -= 操作符 (§的右手操作數使用。這些操作符被用來為事件成員附加或去掉事件句柄,而這個事件成員的訪問操作符控制操作在其中被執行的上下文。
由於+= 和 -= 是唯一可以在聲明了事件成員的類型外的事件上使用的操作,外部代碼可以為一個事件添加或去掉句柄,但是不能通過任何其他途徑獲得或修改基本事件域或事件屬性的數值。
在例子中
public delegate void EventHandler(object sender, Event e);
public class Button: Control
{
public event EventHandler Click;
protected void OnClick(Event e) {
if (Click != null) Click(this, e);
}
public void Reset() {
Click = null;
}
}
對使用Button類中的Click事件域沒有限制。作為演示的例子,這個域可以在代表調用表達式中被檢驗、修改和使用。類Button中的OnClick方法"引起"Click事件。引起一個事件的概念與調用由事件成員表示的代表正好相同-因此,對於引起事件沒有特殊的語言構造。注意代表的調用是通過檢查保證代表是非空後才進行的。
在類Button的聲明外面,成員Click只能被用在+= 和 -= 操作符右手邊,如下
b.Click += new EventHandler(...);
它把一個代表附加到事件Click的調用列表中,並且
b.Click -= new EventHandler(...);
它把一個代表從Click事件的調用列表中刪除。
在一個形式為x += y 或 x -= y的操作中,當x是一個事件成員而引用在包含x的聲明的類型外面發生時,操作的結果就是void(在賦值後與x的數值相反)。這個規則禁止外部代碼直接檢查事件成員的基本代表。
下面的例子介紹了事件句柄如何附加到上面的類Button的實例中:
public class LoginDialog: Form
{
Button OkButton;
Button CancelButton;
public LoginDialog() {
OkButton = new Button(...);
OkButton.Click += new EventHandler(OkButtonClick);
CancelButton = new Button(...);
CancelButton.Click += new EventHandler(CancelButtonClick);
}
void OkButtonClick(object sender, Event e) {
// Handle OkButton.Click event
}
void CancelButtonClick(object sender, Event e) {
// Handle CancelButton.Click event
}
}
這裡,構造函數LoginDialog創建了兩個Button實例,並且把事件句柄附加到事件Click中。
事件成員是典型域,就像上面的Button例子中所示。在每個事件消耗一個域存儲的情況是不可接受的,一個類可以聲明事件屬性來替代事件域,並且使用私有機制來存儲基本的代表。(設想在某種情況下,大多數事件都是未處理的,每個事件使用一個域就不能被接受。使用屬性而不是域的能力允許開發人員在空間和時間上面取得一個折中方案。)
在例子中
class Control: Component
{
// Unique keys for events
static readonly object mouseDownEventKey = new object();
static readonly object mouseUpEventKey = new object();
// Return event handler associated with key
protected Delegate GetEventHandler(object key) {...}
// Set event handler associated with key
protected void SetEventHandler(object key, Delegate handler) {...}
// MouseDown event property
public event MouseEventHandler MouseDown {
get {
return (MouseEventHandler)GetEventHandler(mouseDownEventKey);
}
set {
SetEventHandler(mouseDownEventKey, value);
}
}
// MouseUp event property
public event MouseEventHandler MouseUp {
get {
return (MouseEventHandler)GetEventHandler(mouseUpEventKey);
}
set {
SetEventHandler(mouseUpEventKey, value);
}
}
}
類Control為事件提供了一種內部存儲機制。方法SetEventHandler用一個key來與代表數值相關,而方法GetEventHandler返回與key相關的當前代表。大概基本的存儲機制是按照把空代表類型與key相關不會有消耗而設計的,因此無句柄的事件不占用存儲空間。
實例變量初始化函數
當一個構造函數沒有構造初始化函數或一個形式為base(...)的構造函數初始化函數,構造函數就就隱含的執行被類中聲明的實例域的變量初始化函數指定的初始化。這與賦值序列相關,這些賦值在直接基類構造函數的隱式調用前,在構造函數的入口被直接執行。變量初始化函數按照它們在類聲明中出現的文字順序執行。
構造函數執行
可以把一個實例變量初始化函數和一個構造函數初始化函數,看作是自動插在構造函數主體中的第一條語句前。例子
using System.Collections;
class A
{
int x = 1, y = -1, count;
public A() {
count = 0;
}
public A(int n) {
count = n;
}
}
class B: A
{
double sqrt2 = Math.Sqrt(2.0);
ArrayList items = new ArrayList(100);
int max;
public B(): this(100) {
items.Add("default");
}
public B(int n): base(n - 1) {
max = n;
}
}
包含了許多變量初始化函數,並且也包含了每個形式(base和this)的構造函數初始化函數。這個例子與下面介紹的例子相關,在那裡,每條注釋指明了一個自動插入的語句(自動插入構造函數調用所使用的語法不是有效的,至少用來演示這個機制)。
using System.Collections;
class A
{
int x, y, count;
public A() {
x = 1; // Variable initializer
y = -1; // Variable initializer
object(); // Invoke object() constructor
count = 0;
}
public A(int n) {
x = 1; // Variable initializer
y = -1; // Variable initializer
object(); // Invoke object() constructor
count = n;
}
}
class B: A
{
double sqrt2;
ArrayList items;
int max;
public B(): this(100) {
B(100); // Invoke B(int) constructor
items.Add("default");
}
public B(int n): base(n - 1) {
sqrt2 = Math.Sqrt(2.0); // Variable initializer
items = new ArrayList(100); // Variable initializer
A(n - 1); // Invoke A(int) constructor
max = n;
}
}
注意變量初始化函數被轉換為賦值語句,並且那個賦值語句在對基類構造函數調用前執行。這個順序確保了所有實例域在任何訪問實例的語句執行前,被它們的變量初始化函數初始化。例如:
class A
{
public A() {
PrintFields();
}
public virtual void PrintFields() {}
}
class B: A
{
int x = 1;
int y;
public B() {
y = -1;
}
public override void PrintFields() {
Console.WriteLine("x = {0}, y = {1}", x, y);
}
}
當new B() 被用來創建B的實例時,產生下面的輸出:
x = 1, y = 0
因為變量初始化函數在基類構造函數被調用前執行,所以x的數值是1。可是,y的數值是0(int的默認數值),這是因為對y的賦值直到基類構造函數返回才被執行。
默認構造函數
如果一個類不包含任何構造函數聲明,就會自動提供一個默認的構造函數。默認的構造函數通常是下面的形式
public C(): base() {}
這裡C是類的名稱。默認構造函數完全調用直接基類的無參數構造函數。如果直接基類中沒有可訪問的無參數構造函數,就會發生錯誤。在例子中
class Message
{
object sender;
string text;
}
因為類不包含構造函數聲明,所以提供了一個默認構造函數。因此,這個例子正好等同於
class Message
{
object sender;
string text;
public Message(): base() {}
}
私有構造函數
當一個類只聲明了私有的構造函數時,其他類就不能從這個類派生或創建類的實例。私有構造函數通常用在只包含靜態成員的類中。例如:
public class Trig
{
private Trig() {} // Prevent instantiation
public const double PI = 3.14159265358979323846;
public static double Sin(double x) {...}
public static double Cos(double x) {...}
public static double Tan(double x) {...}
}
Trig 類提供了一組相關的方法和常數,但沒有被例示。因此,它聲明了一個單獨的私有構造函數。注意至少要必須聲明一個私有構造函數來避免自動生成默認的構造函數(它通常有公共的訪問性)。
可選的構造函數參數
構造函數的this(...) 形式通常用於與實現可選的構造函數參數的關聯上。在這個例子中
class Text
{
public Text(): this(0, 0, null) {}
public Text(int x, int y): this(x, y, null) {}
public Text(int x, int y, string s) {
// Actual constructor implementation
}
}
前兩個構造函數只是為丟失的參數提供了默認的數值。兩個都使用了一個this(...)構造函數的初始化函數來調用第三個構造函數,它實際上做了對新實例進行初始化的工作。效果是那些可選的構造函數參數:
Text t1 = new Text(); // Same as Text(0, 0, null)
Text t2 = new Text(5, 10); // Same as Text(5, 10, null)
Text t3 = new Text(5, 20, "Hello");
析構函數
析構函數是一個實現破壞一個類的實例的行為的成員。析構函數使用析構函數聲明來聲明:
一個析構函數聲明的標識符必須為聲明析構函數的類命名,如果指定了任何其他名稱,就會發生一個錯誤。
析構函數聲明的主體指定了為了對類的新實例進行初始化而執行的語句。這於一個有void返回類型的實例方法的主體相關。
例子
class Test
{
static void Main() {
A.F();
B.F();
}
}
class A
{
static A() {
Console.WriteLine("Init A");
}
public static void F() {
Console.WriteLine("A.F");
}
}
class B
{
static B() {
Console.WriteLine("Init B");
}
public static void F() {
Console.WriteLine("B.F");
}
}
會產生或者是下面的輸出:
Init A
A.F
Init B
B.F
或者是下面的輸出:
Init B
Init A
A.F
B.F