成员的声明可用于控制对该成员的访问。成员的可访问性是由该成员的声明可访问性和直接包含它的那个类型的可访问性(若它存在)结合起来确定的。
如果允许访问特定成员,则称该成员是可访问的 (accessible)。
相反,如果不允许访问特定成员,则称该成员是不可访问的 (inaccessible)。当引发访问的源代码的文本位置在某成员的可访问域中时,允许对该成员进行访问。
⟰ 已声明可访问性
成员的已声明可访问性 (declared accessibility) 可以是下列类型之一:
✹ Public,选择它的方法是在成员声明中包括 public 修饰符。public 的直观含义是“访问不受限制”。
✹ Protected,选择它的方法是在成员声明中包括 protected 修饰符。protected 的直观含义是“访问范围限定于它所属的类或从该类派生的类型”。
✹ Internal,选择它的方法是在成员声明中包括 internal 修饰符。internal 的直观含义是“访问范围限定于此程序”。
✹ Protected internal(意为受保护或内部的),选择它的方法是在成员声明中包括 protected 和 internal 修饰符。protected internal 的直观含义是“访问范围限定于此程序或那些由它所属的类派生的类型”。
✹ Private,选择它的方法是在成员声明中包括 private 修饰符。private 的直观含义是“访问范围限定于它所属的类型”。 声明一个成员时所能选择的已声明可访问性的类型,依赖于该成员声明出现处的上下文。此外,当成员声明不包含任何访问修饰符时,声明发生处的上下文会为该成员选择一个默认的已声明可访问性。
✹ 命名空间隐式具有 public 已声明可访问性。在命名空间声明中不允许使用访问修饰符。
✹ 编译单元或命名空间中声明的类型可以具有 public 或 internal 已声明可访问性,默认的已声明可访问性为 internal。
✹ 类成员可具有五种已声明可访问性中的任何一种,默认为 private 已声明可访问性。(请注意,声明为类成员的类型可具有五种已声明可访问性中的任何一种,而声明为命名空间成员的类型只能具有 public 或 internal 已声明可访问性。)
✹ 结构成员可以具有 public、internal 或 private 已声明可访问性并默认为 private 已声明可访问性,这是因为结构是隐式密封的。结构的成员若是在此结构中声明的(也就是说,不是由该结构从它的基类中继承的),则不能具有 protected 或 protected internal 已声明可访问性。(请注意,声明为结构成员的类型可具有 public、internal 或 private 已声明可访问性,而声明为命名空间成员的类型只能具有 public 或 internal 已声明可访问性。)
✹ 接口成员隐式地具有 public 已声明可访问性。在接口成员声明中不允许使用访问修饰符。
✹ 枚举成员隐式地具有 public 已声明可访问性。在枚举成员声明中不允许使用访问修饰符。
⟰ 可访问域
一个成员的可访问域 (accessibility domain) 由(可能是不连续的)程序文本节组成,从该域中可以访问该成员。出于定义成员可访问域的目的,
如果成员不是在某个类型内声明的,就称该成员是顶级 (top- level) 的;
如果成员是在其他类型内声明的,就称该成员是嵌套 (nested) 的。
此外,程序的程序文本 (program text) 定义为包含在该程序的所有源文件中的全部程序文本,而类型的程序文本定义为包含在该类型(可能还包括该类型中的嵌套类型)的 type-declaration 中的所有程序文本。 预定义类型(如 object、int 或 double)的可访问域无限制。
在程序 P 中声明的顶级未绑定类型 T的可访问域定义如下:
✹ 如果 T 的已声明可访问性为 public,则 T 的可访问域是 P 以及引用 P 的任何程序的程序文本。
✹ 如果 T 的已声明可访问性为 internal,则 T 的可访问域就是 P 的程序文本。 从这些定义可以推断出:顶级未绑定类型的可访问域始终至少是声明了该类型的程序的程序文本。 构造类型 T<a1, ...,an=""> 的可访问域是未绑定的泛型类型 T 的可访问域和类型实参 A1, ...,AN 的可访问域 的交集。 在程序 P 内的类型 T 中声明的嵌套成员 M 的可访问域定义如下(注意 M 本身可能就是一个类型):
✹ 如果 M 的已声明可访问性为 public,则 M 的可访问域就是 T 的可访问域。
✹ 如果 M 的已声明可访问性是 protected internal,则设 D 表示 P 的程序文本和从 T(在 P 外部声 明)派生的任何类型的程序文本的并集。M 的可访问域是 T 与 D 的可访问域的交集。
✹ 如果 M 的已声明可访问性是 protected,则设 D 表示 T 的程序文本和从 T 派生的任何类型的程序文 本的并集。M 的可访问域是 T 与 D 的可访问域的交集。
✹ 如果 M 的已声明可访问性为 internal,则 M 的可访问域就是 T 的可访问域与 P 的程序文本之间的交集。
✹ 如果 M 的已声明可访问性为 private,则 M 的可访问域就是 T 的程序文本。 从这些定义可以看出:嵌套成员的可访问域总是至少为声明该成员的类型的程序文本。还可以看出:成员的可访问域包含的范围决不会比声明该成员的类型的可访问域更广。 直观地讲,当访问类型或成员 M 时,按下列步骤进行计算以确保允许进行访问:
✹ 首先,如果 M 是在某个类型(相对于编译单元或命名空间)内声明的,则当该类型不可访问时将会发生编译时错误。
✹ 然后,如果 M 为 public,则允许进行访问。
✹ 否则,如果 M 为 protected internal,则当访问发生在声明了 M 的程序中,或发生在从声明 M 的类派生的类中并通过派生类类型进行访问时,允许进行访问。
✹ 否则,如果 M 为 protected,则当访问发生在声明了 M 的类中,或发生在从声明 M 的类派生的类中并通过派生类类型进行访问时,允许进行访问。
✹ 否则,如果 M 为 internal,则当访问发生在声明了 M 的程序中时允许进行访问。
✹ 否则,如果 M 为 private,则当访问发生在声明了 M 的类型中时允许进行访问。
✹ 否则,类型或成员不可访问,并发生编译时错误。 在下面的示例中
public class A { public static int X; internal static int Y; private static int Z; } internal class B { public static int X; internal static int Y; private static int Z; public class C { public static int X; internal static int Y; private static int Z; } private class D { public static int X; internal static int Y; private static int Z; } }
类和成员具有下列可访问域:
✹ A 和 A.X 的可访问域无限制。
✹ A.Y、B、B.X、B.Y、B.C、B.C.X 和 B.C.Y 的可访问域是包含程序的程序文本。
✹ A.Z 的可访问域是 A 的程序文本。
✹ B.Z 和 B.D 的可访问域是 B 的程序文本,包括 B.C 和 B.D 的程序文本。
✹ B.C.Z 的可访问域是 B.C 的程序文本。
✹ B.D.X 和 B.D.Y 的可访问域是 B 的程序文本,包括 B.C 和 B.D 的程序文本。
✹ B.D.Z 的可访问域是 B.D 的程序文本。 如示例所示,成员的可访问域绝不会大于包含它的类型的可访问域。例如,即使所有的 X 成员都具有公共级的已声明可访问性,但除了 A.X 外,所有其他成员的可访问域都受包含类型的约束。 基类的所有成员(实例构造函数、析构函数和静态构造函数除外)都由派生类型继承。这甚至包括基类的私有成员。但是,私有成员的可访问域只包括声明该成员的类型的程序 文本。在下面的示例中
class A { int x; static void F(B b) { b.x = 1; // Ok } } class B: A { static void F(B b) { b.x = 1; // Error, x not accessible } }
类 B 继承类 A 的私有成员 x。因为该成员是私有的,所以只能在 A 的 class-body 中对它进行访问。因此, 对 b.x 的访问在 A.F 方法中取得了成功,在 B.F 方法中却失败了。
⟰ 实例成员的受保护访问
当在声明了某个 protected 实例成员的类的程序文本之外访问该实例成员时,以及当在声明了某个 protected internal 实例成员的程序的程序文本之外访问该实例成员时,这种访问必须发生在声明了该成员的类的一个派生类的类声明中。而且,要求这种访问通过该成员所属类的派生类类型的实例或从它构造的类类型的实例发生。此限制阻止一个派生类访问其他派生类的受保护成员,即使成员继承自同一个基类也是如此。 假定 B 是一个基类,它声明了一个受保护的实例成员 M,而 D 是从 B 派生的类。在 D 的 class-body 中, 对 M 的访问可采取下列形式之一:
✹ M 形式的非限定 type-name 或 primary-expression。
✹ E.M 形式的 primary-expression,假定 E 的类型是 T 或从 T 派生的类,其中 T 为类类型 D 或从 D 构造的类类型
✹ base.M 形式的 primary-expression。
除了上述访问形式外,派生类还可以在 constructor-initializer 中访问基类的受保护的实例构造函数。 在下面的示例中
public class A { protected int x; static void F(A a, B b) { a.x = 1; // Ok b.x = 1; // Ok } } public class B: A { static void F(A a, B b) { a.x = 1; // Error, must access through instance of B b.x = 1; // Ok } }
在 A 中可以通过 A 和 B 的实例访问 x,这是因为在两种情况下访问都通过 A 的实例或从 A 派生的类发生。 但是在 B 中,由于 A 不从 B 派生,所以不可能通过 A 的实例访问 x。 在下面的示例中
class C<T> { protected T x; } class D<T>: C<T> { static void F() { D<T> dt = new D<T>(); D<int> di = new D<int>(); D<string> ds = new D<string>(); dt.x = default(T); di.x = 123; ds.x = "test"; } }
对 x 的三个赋值是允许的,因为它们全都通过从该泛型类型构造的类类型的实例进行。
⟰ 可访问性约束
C# 语言中的有些构造要求某个类型至少与某个成员或其他类型具有同样的可访问性 (at least as accessible as)。
如果 T 的可访问域是 M 可访问域的超集,我们就说类型 T 至少与成员或类型 M 具有同样的可访问性。
换言之,如果 T 在可访问 M 的所有上下文中都是可访问的,则 T 至少与 M 具有同样的可访问性。 存在下列可访问性约束:
✹ 类类型的直接基类必须至少与类类型本身具有同样的可访问性。
✹ 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。
✹ 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。
✹ 常量的类型必须至少与常量本身具有同样的可访问性。
✹ 字段的类型必须至少与字段本身具有同样的可访问性。
✹ 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。
✹ 属性的类型必须至少与属性本身具有同样的可访问性。
✹ 事件的类型必须至少与事件本身具有同样的可访问性。
✹ 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。
✹ 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。
✹ 实例构造函数的参数类型必须至少与实例构造函数本身具有同样的可访问性。
在下面的示例中
class A {...} public class B: A {...}
B 类导致编译时错误,因为 A 并不具有至少与 B 相同的可访问性。 同样,在示例中
class A {...} public class B { A F() {...} internal A G() {...} public A H() {...} }
B 中的方法 H 导致编译时错误,因为返回类型 A 并不具有至少与该方法相同的可访问性。