一個事件是一個使對象或類可以提供公告的成員。用戶可以通過提供事件句柄來為事件添加可執行代碼。事件使用事件聲明來聲明:
一個事件聲明既可以是一個事件域聲明也可以是事件屬性聲明。在每種情況中,聲明都可以由屬性集合, 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的實例中:
類Control為事件提供了一種內部存儲機制。方法SetEventHandler用一個key來與代表數值相關,而方法GetEventHandler返回與key相關的當前代表。大概基本的存儲機制是按照把空代表類型與key相關不會有消耗而設計的,因此無句柄的事件不占用存儲空間。
實例變量初始化函數
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) {...}
}