一个事件是一个使对象或类可以提供公告的成员用户可以通过提供事件句柄来为事件添加可执行代码事件使用事件声明来声明
一个事件声明既可以是一个事件域声明也可以是事件属性声明在每种情况中声明都可以由属性集合 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只能被用在+= 和 = 操作符右手边如下
bClick += new EventHandler();
它把一个代表附加到事件Click的调用列表中并且
bClick = 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();
OkButtonClick += new EventHandler(OkButtonClick);
CancelButton = new Button();
CancelButtonClick += new EventHandler(CancelButtonClick);
}
void OkButtonClick(object sender Event e) {
// Handle OkButtonClick event
}
void CancelButtonClick(object sender Event e) {
// Handle CancelButtonClick 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 SystemCollections;
class A
{
int x = y = count;
public A() {
count = ;
}
public A(int n) {
count = n;
}
}
class B: A
{
double sqrt = MathSqrt();
ArrayList items = new ArrayList();
int max;
public B(): this() {
itemsAdd(default);
}
public B(int n): base(n ) {
max = n;
}
}
包含了许多变量初始化函数并且也包含了每个形式(base和this)的构造函数初始化函数这个例子与下面介绍的例子相关在那里每条注释指明了一个自动插入的语句(自动插入构造函数调用所使用的语法不是有效的至少用来演示这个机制)
using SystemCollections;
class A
{
int x y count;
public A() {
x = ; // Variable initializer
y = ; // Variable initializer
object(); // Invoke object() constructor
count = ;
}
public A(int n) {
x = ; // Variable initializer
y = ; // Variable initializer
object(); // Invoke object() constructor
count = n;
}
}
class B: A
{
double sqrt;
ArrayList items;
int max;
public B(): this() {
B(); // Invoke B(int) constructor
itemsAdd(default);
}
public B(int n): base(n ) {
sqrt = MathSqrt(); // Variable initializer
items = new ArrayList(); // Variable initializer
A(n ); // Invoke A(int) constructor
max = n;
}
}
注意变量初始化函数被转换为赋值语句并且那个赋值语句在对基类构造函数调用前执行这个顺序确保了所有实例域在任何访问实例的语句执行前被它们的变量初始化函数初始化例如
class A
{
public A() {
PrintFields();
}
public virtual void PrintFields() {}
}
class B: A
{
int x = ;
int y;
public B() {
y = ;
}
public override void PrintFields() {
ConsoleWriteLine(x = {} y = {} x y);
}
}
当new B() 被用来创建B的实例时产生下面的输出
x = y =
因为变量初始化函数在基类构造函数被调用前执行所以x的数值是可是y的数值是(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 = ;
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( null) {}
public Text(int x int y): this(x y null) {}
public Text(int x int y string s) {
// Actual constructor implementation
}
}前两个构造函数只是为丢失的参数提供了默认的数值两个都使用了一个this()构造函数的初始化函数来调用第三个构造函数它实际上做了对新实例进行初始化的工作效果是那些可选的构造函数参数
Text t = new Text(); // Same as Text( null)
Text t = new Text( ); // Same as Text( null)
Text t = new Text( Hello);
析构函数
析构函数是一个实现破坏一个类的实例的行为的成员析构函数使用析构函数声明来声明
一个析构函数声明的标识符必须为声明析构函数的类命名如果指定了任何其他名称就会发生一个错误
析构函数声明的主体指定了为了对类的新实例进行初始化而执行的语句这于一个有void返回类型的实例方法的主体相关
例子
class Test
{
static void Main() {
AF();
BF();
}
}
class A
{
static A() {
ConsoleWriteLine(Init A);
}
public static void F() {
ConsoleWriteLine(AF);
}
}
class B
{
static B() {
ConsoleWriteLine(Init B);
}
public static void F() {
ConsoleWriteLine(BF);
}
}
会产生或者是下面的输出
Init A
AF
Init B
BF
或者是下面的输出
Init B
Init A
AF
BF