在学习研究命名空间这一命题时,笔者对C#中的嵌套类型(Nested Type ——MSDN)进行了一些研究,在学习中参阅了《深入理解嵌套类及内部类》(JAVA)和《C#嵌套类的使用方法及特性》(C#)两篇文章,在得到指引的同时也发现了《C#嵌套类的使用方法及特性》一文中的不少错误,现在将自己的这些笔记整理发布。
一 前言
首先简单介绍一下嵌套类型,简单来说,嵌套类型就是在一个类型中声明的类型,在形式上是外层类型的一个成员。比如:
class MorthType
{
class SonType
{ }
}
嵌套类型,在C++和JAVA中也有类似的形式,但使用起来却和C#有很大不同。
在C++中,嵌套类型的成员函数对外层类型(包含它的类型)的成员没有特殊的访问权,反之亦然。在C++中嵌套关系强调的是主从关系,嵌套类型与外层类型只是语法和语义上的嵌入,其实质上和两个分开的类型差别并不大。
JAVA将嵌套类型进行了扩展,加强了外层类型和嵌套类型的逻辑关系,并且将嵌套类型详细的划分为静态嵌套类型和非静态嵌套类型,由于静态嵌套类型和一个额外的新类型差别不大,非静态嵌套类型用处较大,便赋予非静态嵌套类型别称内部类型,而将外层类型称为外部类型。这在语义和逻辑上使嵌套类型成为外层类型的私有成员,并赋予内部类型大量的有用特性。
具体细节,可以参照http://blog.csdn.net/acumengrass/archive/2005/11/27/537686.aspx 《深入理解嵌套类及内部类》一文。
二 C#中的嵌套类型
在写本文时,笔者还参考了一篇文章http://blog.sina.com.cn/s/blog_4c8a4e5901009lnv.html 《C#嵌套类的使用方法及特性》,这篇文章给予我很多指引,但其中有不少我不能苟同之处,我也将在本文中阐述我的不同见解。
在MSDN的“嵌套类型”一节中,外层类型被称为“声明类型”、“包含类型”或“外部类型”,嵌套类型被称为“嵌套类型”或“内部类型”。国内很多翻译书籍将外层类型翻译为“外层类型(outer type)”,将嵌套类型翻译为“嵌套类型(nested type)”为了不造成混淆,本文中将使用这对名称。
《Programming C#》中文第4版一书中,作者明确指出:嵌套类型略等价于Java的静态内部类型,但C#中不存在Java非静态内部类型的等价物。
所以《C#嵌套类的使用方法及特性》一文中将JAVA中对嵌套类型的定义直接套用,笔者认为这是不正确的。
C#中的嵌套类型不再有JAVA内部类型的部分特征,这些不支持的特性JAVA程序员请注意:
1,无法在一个外层类型的方法中声明一个嵌套类型;
2,无法声明匿名的嵌套类型;
下面将仔细分析C#中的嵌套类型的特性和使用规则。
C#嵌套类型特性:
1 可访问性:
嵌套类型可以自由访问外层类型的成员,而不在乎那些成员是否私有。外层类型如果想访问嵌套类型,则要受到访问规则的限制。
不管外层类型是类型还是结构,嵌套类型均默认为 private,但是可以设置为 public、protected internal、protected、internal 或 private。
如果嵌套类型是private或protected的,嵌套类型对外层类型以外的其它类型是不可访问的,外层类型和在外层类型中的另一个嵌套类型却可以访问它,但是无法使用它的private或protected成员。如果嵌套类型中的成员设置为 public或者internal,那么外层类型和其它同一个外层类型声明的嵌套类型可以访问嵌套类型的这些成员,但这些成员对外层类型以外的类型依旧隐藏。
如果嵌套类型是public或者internal的,那么它将和一个普通的类型类似,其他类型可以使用MorthType.SonType的形式来正常的使用它,但这个嵌套类型依旧可以自由访问外层类型的所有成员。其实这在赋予一个类型强大的访问性的同时,却使一个类型的私有成员暴露出去,破坏了类型的封装性。此时外层类型功能上类型似一个命名空间,这也赋予类型一些命名空间的功能。
2 互访问方式和反驳“懒加载”理论:
《C#嵌套类的使用方法及特性》一文中通过一个实例说明嵌套类型是懒加载的,也就是原则上嵌套类型是在真正使用它时才会初始化,它不会在外层类型实例化时实例化。但这个例子本身其实并不能证明其论点。
原例如下:
[开始引用]
嵌套类的静态构造函数不会随着外部类的触发而初始化。因此可以有效地避免创建时候初始化时间,当需要使用内嵌类的时候,嵌套类才开始初始化才开始初始化。
public classOutside
{
static Outside()
{
Console.WriteLine("Outside Inilizlized");
}
public void SayIt()
{
Nested.Run();
}
private class Nested
{
static Nested()
{
Console.WriteLine("Nested initilized");
}
public staticvoid Run()
{
Console.WriteLine("Nested Run");
}
}
}
执行结果
Outside o = new Outside();//打印"Outside Inilizlized"
Console.ReadLine();
o.SayIt();//首先打印"Nested initilized"再打印 "Nested Run"
Console.ReadLine();
[结束引用]
以上实例运行结果确实如作者所说,但这并非嵌套类型的特征。
实例中的嵌套类型使用的是静态构造函数,被调用的是静态方法,而在MSDN中的“构造函数[C#],静态”一节中对静态构造函数的描述“静态构造函数用于初始化任何静态数据,或用于执行仅需执行一次的特定操作。在创建第一个实例或引用任何静态成员之前,将自动调用静态构造函数。”
MSDN中给出的实例即使用普通类型实现了这一特性,所以《C#嵌套类型的使用方法及特性》一文中用此特性证明嵌套类型是懒加载有失偏颇。
本文同样给予代码例证(普通类型):
public classOutside
{
public Outside()
{
Console.WriteLine("Outside Inilizlized");
}
public void SayIt()
{
Nested.Run();
}
}
public classNested
{
static Nested()
{
Console.WriteLine("Nested initilized");
}
public staticvoid Run()
{
Console.WriteLine("Nested Run");
}
}
执行结果完全相同。因此这种“懒加载”是静态构造函数的特性,和嵌套类型无关。
除了访问规则的特殊性,外层类型和嵌套类型之间互相访问方式和普通的两个类型之间互相访问方式相同,都是先将一个类型实例化,然后将这个对象以参数的方式传递进另一个类型的方法中;如果使用的是静态成员,则同样不需要实例化,直接以类型名引用即可,但由于嵌套类型和外层类型处于同一个域中,因此一般可以不写外层类型的类型名。但如果内部类型也定义了和外部类型同名的成员,则必须使用完全限定名,否则内部类型在使用这个名称的成员时将使用自己的成员。
实例,来源于MSDN:
public classContainer
{
public classNested
{
private Container parent;
public Nested(Container parent)
{
this.parent = parent;
}
}
}
3 继承:
现在讨论继承的问题。外层类型无法继承它自己声明的嵌套类型,这会引发循环嵌套的错误。
一个类型可以继承另一个类型声明的为public的嵌套类型,除此之外任何修饰符都将隐藏这些嵌套类型,包括internal。由于前面介绍过的原因,这样的继承破坏类型的封装性,因此只能当做必杀技而非常规武器。
如果一个类型继承了一个包含嵌套类型的类型,那么它也会自然的继承基类型中的嵌套类型,因为那个嵌套类型本身就是基类型的一个成员。因此只要那个嵌套类型不是private或者天生不能被继承的,子类型中新声明的嵌套类型还可以继承那个嵌套类型。
三 C#中的嵌套类型的使用规则
现在附上MSDN中关于使用嵌套函数的描述,这里较完整的为嵌套类型的使用时机进行了说明。
[开始引用]
嵌套类型是作为某其他类型的成员的类型。嵌套类型应与其外层类型紧密关联,并且不得用作通用类型。有些开发人员会将嵌套类型弄混淆,因此嵌套类型不应是公开可见的,除非不得不这样做。在设计完善的库中,开发人员几乎不需要使用嵌套类型实例化对象或声明变量。
在外层类型使用和创建嵌套类型实例时,嵌套类型很有用,但不在公共成员中公开嵌套类型的使用。
如果嵌套类型和其外部类型之间的关系需要成员可访问性语义,则要使用嵌套类型。
由于嵌套类型被视为是外层类型的成员,因此嵌套类型可以访问外层类型中的所有其他成员。
如果可能在外层类型的外部引用类型,则不要使用嵌套类型。
在常见方案中,不应要求对嵌套类型进行变量声明和对象实例化。例如,处理在某一类型上定义的事件的事件处理程序委托不应嵌套在该类型中。
如果需要由客户端代码实例化类型,则不要使用嵌套类型。如果某种类型具有公共构造函数,就可能不应进行嵌套。
理想情况下,嵌套类型仅由它的外层类型进行实例化和使用。如果嵌套类型具有公共构造函数,则表示该类型不单由其外层类型使用。通常情况下,嵌套类型不应针对其外层类型以外的类型执行任务。如某种类型具有更广泛的用途,就很可能不应进行嵌套。
不要将嵌套类型定义为接口的成员。许多语言不支持这样的构造。”