具有入口点 (entry point) 的程序集称为应用程序 (application)。应用程序运行时,将创建新的应用程序域 (application domain)。同一台计算机上可能会同时运行着同一个应用程序的若干个实例,此时,每一个实例都拥有各自的应用程序域。
应用程序域用作应用程序状态的容器,以此隔离应用程序。应用程序域作为应用程序中和它使用的类库中所定义的类型的容器和边界。同一个类型若被加载到不同的应用程序域中就成为各自独立的客体,由它们在各自应用程序域中产生的实例亦不可直接共享。例如,对于这些类型的静态变量,每个应用程序域都有自己的副本,并且这些类型的静态构造函数在每个应用程序域中也要(最多)运行一次。关于如何处理程序域的创建和销毁,各实现可以按具体情况确定自己的策略或机制。
当执行环境调用指定的方法(称为应用程序的入口点)时发生应用程序启动 (application startup)。此入口点方法总是被命名为 Main,可以具有下列签名之一:
static void Main() {
...}
static void Main(string[] args) {
...}
static int Main() {
...}
static int Main(string[] args) {
...}
如上所示,入口点可以选择返回一个 int 值。此返回值用于应用程序终止。
入口点可以包含一个形参(可选)。该参数可以具有任意名称,但参数的类型必须为 string[]。如果存在形参,执行环境会创建并传递一个包含命令行实参的 string[] 实参,这些命令行实参是在启动应用程序时指定的。string[] 参数永远不能为 null,但如果没有指定命令行参数,它的长度可以为零。
由于 C# 支持方法重载,因此类或结构可以包含某个方法的多个定义(前提是每个定义有不同的签名)。但在一个程序内,没有任何类或结构可以包含一个以上的名为 Main 的方法,因为 Main 的定义限定它只能被用作应用程序的入口点。允许使用 Main 的其他重载版本,前提是它们具有一个以上的参数,或者它们的唯一参数的类型不是 string[]。
应用程序可由多个类或结构组成。在这些类或结构中,可能会有若干个拥有自己的 Main 方法,因为 Main 的定义限定它只能被用作应用程序的入口点。这样的情况下,必须利用某种外部机制(如命令行编译器的选项)来选择其中一个 Main 方法用作入口点。
在 C# 中,每个方法都必须定义为类或结构的成员。通常,方法的已声明可访问性由其声明中指定的访问修饰符确定。同样,类型的已声明可访问性由其声明中指定的访问修饰符确定。为了能够调用给定类型的给定方法,类型和成员都必须是可访问的。然而,应用程序入口点是一种特殊情况。具体而言,执行环境可以访问应用程序的入口点,无论它本身的可访问性和封闭它的类型的可访问性是如何在声明语句中设置的。
应用程序入口点方法不能位于泛型类声明中。
在所有其他方面,入口点方法的行为与非入口点方法类似。
应用程序终止 (application termination) 将控制返回给执行环境。
如果应用程序的入口点 (entry point) 方法的返回类型为 int,则返回的值用作应用程序的终止状态代码 (termination status code)。此代码的用途是允许与执行环境进行关于应用程序运行状态(成功或失败)的通信。
如果入口点方法的返回类型为 void,那么在到达终止该方法的右大括号 (}),或者执行不带表达式的 return 语句时,将产生终止状态代码 0。
在应用程序终止之前,将调用其中还没有被垃圾回收的所有对象的析构函数,除非已将这类清理功能设置为取消使用(例如,通过调用库方法 GC.SuppressFinalize)。
C# 程序中的声明定义程序的构成元素。C# 程序是用命名空间组织起来的,一个命名空间可以包含类型声明和嵌套的命名空间声明。类型声明用于定义类、结构、接口、枚举和委托。在一个类型声明中可以使用哪些类型作为其成员,取决于该类型声明的形式。例如,类声明可以包含常量声明、字段声明、方法声明、属性声明、事件声明、索引器声明、运算符声明、实例构造函数声明、静态构造函数声明、析构函数声明和嵌套类型声明。
一个声明在它自已所属的那个声明空间 (declaration space) 中定义一个名称。除非是重载成员,否则,在同一个声明空间下若有两个以上的声明语句声明了具有相同名称的成员,就会产生编译时错误。同一个声明空间内绝不能包含不同类型的同名成员。例如,声明空间绝不能包含同名的字段和方法。
有若干种不同类型的声明空间,如下所述。
namespace Megacorp.Data
{
class Customer
{
...
}
}
namespace Megacorp.Data
{
class Order
{
...
}
}
上面的两个命名空间声明为同一声明空间提供了成员,在本例中它们分别声明了具有完全限定名 Megacorp.Data.Customer 和 Megacorp.Data.Order 的两个类。由于两个声明共同构成同一个声明空间,因此如果每个声明中都包含一个同名类的声明,则将导致编译时错误。
正如上面所述,块的声明空间包括所有嵌套块。因此,在下面的示例中,F 和 G 方法导致编译时错误,因为名称 i 是在外部块中声明的,不能在内部块中重新声明。但方法 H 和 I 都是有效的,因为这两个 i 是在单独的非嵌套块中声明的。
class A
{
void F()
{
int i = 0;
if (true)
{
int i = 1;
}
}
void G()
{
if (true)
{
int i = 0;
}
int i = 1;
}
void H()
{
if (true)
{
int i = 0;
}
if (true)
{
int i = 1;
}
}
void I()
{
for (int i = 0; i < 10; i++)
H();
for (int i = 0; i < 10; i++)
H();
}
}
命名空间和类型具有成员 (member)。通常可以通过限定名来访问实体的成员。限定名以对实体的引用开头,后跟一个“.”标记,再接成员的名称。
类型的成员或者是在该类型声明中声明的,或者是从该类型的基类继承 (inherit) 的。当类型从基类继承时,基类的所有成员(实例构造函数、析构函数和静态构造函数除外)都成为派生类型的成员。基类成员的声明可访问性并不控制该成员是否可继承:继承性可扩展到任何成员,只要它们不是实例构造函数、静态构造函数或析构函数。然而,在派生类型中可能不能访问已被继承的成员,原因或者是因为其已声明可访问性,或者是因为它已被类型本身中的声明所隐藏。
命名空间和类型若没有封闭它的命名空间,则属于全局命名空间 (global namespace) 的成员。这直接对应于全局声明空间中声明的名称。
在某命名空间中声明的命名空间和类型是该命名空间的成员。这直接对应于该命名空间的声明空间中声明的名称。
命名空间没有访问限制。不可能把命名空间设置成私有的、受保护的或内部的,命名空间名称始终是可公开访问的。
结构的成员是在结构中声明的成员以及继承自结构的直接基类 System.ValueType 和间接基类 object 的成员。
简单类型的成员直接对应于结构类型的成员,此简单类型正是该结构的化名:
枚举的成员是在枚举中声明的常量以及继承自枚举的直接基类 System.Enum 和间接基类 System.ValueType 和 object 的成员。
类的成员是在类中声明的成员和从该类的基类(没有基类的 object 类除外)继承的成员。从基类继承的成员包括基类的常量、字段、方法、属性、事件、索引器、运算符和类型,但不包括基类的实例构造函数、析构函数和静态构造函数。基类成员被是否继承与它们的可访问性无关。
类声明可以包含以下对象的声明:常量、字段、方法、属性、事件、索引器、运算符、实例构造函数、析构函数、静态构造函数和类型。
object 和 string 的成员直接对应于它们所化名的类类型的成员:
<1> object 的成员是 System.Object 类的成员。
<2> string 的成员是 System.String 类的成员。
接口的成员是在接口中和该接口的所有基接口中声明的成员。严格地说,类 object 中的成员不是任何接口的成员。但是,通过在任何接口类型中进行成员查找,可获得类 object 中的成员。
数组的成员是从类 System.Array 继承的成员。
委托的成员是从类 System.Delegate 继承的成员。
成员的声明可用于控制对该成员的访问。成员的可访问性是由该成员的声明可访问性和直接包含它的那个类型的可访问性(若它存在)结合起来确定的。
如果允许访问特定成员,则称该成员是可访问的 (accessible)。相反,如果不允许访问特定成员,则称该成员是不可访问的 (inaccessible)。当引发访问的源代码的文本位置在某成员的可访问域中时,允许对该成员进行访问。
成员的已声明可访问性 (declared accessibility) 可以是下列类型之一: