深入理解OOP(第5天):关于C#访问修饰符(Public/Private/Protected/Internal/Sealed/Constants/Static和Readonly字段)的一切






4.90/5 (152投票s)
访问修饰符(或访问限定符)是面向对象语言中的关键字,用于设置类、方法和其他成员的访问权限。访问修饰符是编程语言语法的一个特定部分,用于促进组件的封装。
引言
感谢我的读者们的大力支持,这激励我继续撰写本OOP系列文章。
我们已经涵盖了C#中继承和多态的几乎所有方面。我的文章将重点介绍C#中访问修饰符的几乎所有方面/场景。我们将通过动手实验而不仅仅是理论来学习。我们将以“实验”的形式对章节进行分类,以一种非常不同的方式涵盖我最喜欢的主题——常量。我在这篇文章中的努力将是涵盖相关主题的每一个概念,以便在文章结束时,我们可以自信地说我们“了解C#中访问修饰符的一切”。赶紧投入到OOP中吧。
图片来源:http://en.wikipedia.org/wiki/Colocation_centre
先决条件
我期望本文的读者对C#有非常基本的了解。读者只需了解访问修饰符的定义即可。最后但同样重要的是,一如既往,我希望我的读者能够享受阅读本文的过程。
路线图
让我们回顾一下我们的路线图
- 深入OOP(第1天):多态性和继承(早期绑定/编译时多态)
- 深入OOP(第2天):多态性和继承(继承)
- 深入OOP(第3天):多态性和继承(动态绑定/运行时多态)
- 深入OOP(第4天):多态性和继承(C#中抽象类的一切)
- 深入OOP(第5天):C#中访问修饰符的一切(Public/Private/Protected/Internal/Sealed/Constants/Readonly字段)
- 深入OOP(第6天):理解C#中的Enum(一种实用方法)
- 深入OOP(第7天):C#中的属性(一种实用方法)
- 深入OOP(第8天):C#中的索引器(一种实用方法)
- 深入OOP(第9天):理解C#中的事件(洞察)
- 学习C#(第10天):C#中的委托(一种实用方法)
- 学习C#(第11天):C#中的事件(一种实用方法)
访问修饰符
这次我们从维基百科获取定义
“访问修饰符(或访问限定符)是面向对象语言中的关键字,用于设置类、方法和其他成员的访问权限。访问修饰符是编程语言语法的一个特定部分,用于促进组件的封装。”
正如定义所说,我们可以通过访问修饰符控制类方法和成员的访问权限,让我们逐一详细了解每个访问修饰符。
类级别的Public、Private、Protected
每当我们创建一个类时,我们总是希望有能力决定谁可以访问类的某些成员。换句话说,我们有时需要限制对类成员的访问。一个经验法则是,类的成员可以自由地相互访问。一个类中的方法总是可以不受任何限制地访问同一类中的另一个方法。当我们谈论默认行为时,同一个类被允许完全访问,但没有其他人被允许访问该类的成员。类成员的默认访问修饰符是private
。
要记住的一点:类成员的默认访问修饰符是private
。
让我们做一些动手实验。只需打开您的Visual Studio并在C#中添加一个名为AccessModifiers
的控制台应用程序。您将默认获得一个Program.cs类文件。在同一文件中,添加一个名为Modifiers
的新类,并向其添加以下代码
using System;
namespace AccessModifiers
{
class Modifiers
{
static void AAA()
{
Console.WriteLine("Modifiers AAA");
}
public static void BBB()
{
Console.WriteLine("Modifiers BBB");
AAA();
}
}
class Program
{
static void Main(string[] args)
{
Modifiers.BBB();
}
}
}
所以,您的Program.cs文件就变成了上面代码片段中所示的样子。我们添加了一个类Modifiers
和两个static
方法AAA
和BBB
。方法BBB
被标记为public
。我们从Main
方法调用了BBB
方法。该方法直接通过类名调用,因为它被标记为static
。
当我们运行应用程序时,得到以下输出
输出
Modifiers BBB
Modifiers AAA
BBB
被标记为public
,因此任何人都可以调用和运行它。方法AAA
没有标记任何访问修饰符,这自动使其成为private
,即默认值。private
修饰符对同一类的成员没有影响,因此方法BBB
被允许调用方法AAA
。现在这个概念被称为成员访问。
修改Program
类,尝试访问AAA
如下所示
class Program
{
static void Main(string[] args)
{
Modifiers.AAA();
Console.ReadKey();
}
}
输出
'AccessModifiers.Modifiers.AAA()' is inaccessible due to its protection level
因此,由于方法AAA
是private
的,除了Modifiers
类之外,没有人可以访问它。
注意:本文中的每个代码片段都经过了尝试和测试。
修饰符
现在将AAA
方法标记为protected,我们的类如下所示
class Modifiers
{
protected static void AAA()
{
Console.WriteLine("Modifiers AAA");
}
public static void BBB()
{
Console.WriteLine("Modifiers BBB");
AAA();
}
}
程序
class Program
{
static void Main(string[] args)
{
Modifiers.AAA();
Console.ReadKey();
}
}
输出
'AccessModifiers.Modifiers.AAA()' is inaccessible due to its protection level
输出再次相同。即使我们引入了一个名为protected
的新修饰符,我们也无法访问方法AAA
。但是BBB
可以访问AAA
方法,因为它位于同一个类中。
继承中的修饰符
让我们再添加一个类,并与我们现有的类建立基类和派生类的关系,并向我们的基类添加一个方法。所以我们的类结构将看起来像这样
修饰符基类
class ModifiersBase
{
static void AAA()
{
Console.WriteLine("ModifiersBase AAA");
}
public static void BBB()
{
Console.WriteLine("ModifiersBase BBB");
}
protected static void CCC()
{
Console.WriteLine("ModifiersBase CCC");
}
}
修饰符派生类
class ModifiersDerived:ModifiersBase
{
public static void XXX()
{
AAA();
BBB();
CCC();
}
}
程序类
class Program
{
static void Main(string[] args)
{
ModifiersDerived.XXX();
Console.ReadKey();
}
}
输出
'AccessModifiers.ModifiersBase.AAA()' is inaccessible due to its protection level
在这种情况下,我们正在处理派生类。每当我们用protected
修饰符标记一个方法时,我们实际上是在告诉C#,只有派生类才能访问该方法,其他任何人都不能。因此,在方法XXX中,我们可以调用CCC,因为它被标记为protected
,但它不能从其他任何地方调用,包括Main
函数。方法AAA
被设为private
,只能从ModifiersBase
类中调用。如果我们将AAA
从方法XXX
中删除,编译器将不会给出错误。
因此,现在我们了解了三个重要概念。Private
表示只有同一个类才能访问成员,public
表示所有人都可以访问,而protected
介于两者之间,只有派生类才能访问基类方法。
例如,所有方法都驻留在类中。该方法的访问权限由其所在的类以及方法上的修饰符决定。如果我们被允许访问某个成员,那么我们说该成员是可访问的,否则是不可访问的。
类级别的Internal修饰符
我们再来一个场景。在您的Visual Studio中创建一个名为“AccessModifiersLibrary
”的类库。在该类库中添加一个名为ClassA
的类,并将该类标记为internal
,代码如下所示
AccessModifiersLibrary.ClassA:
namespace AccessModifiersLibrary
{
internal class ClassA
{
}
}
现在编译类,然后离开。它的DLL将在~\AccessModifiersLibrary\bin\Debug文件夹中生成。
现在在您的控制台应用程序“AccessModifiers
”(即之前创建的应用程序)中。通过将AccessModifiersLibrary
库的已编译DLL作为引用添加到AccessModifiers
来添加其引用。
在AccessModifiers
控制台应用程序的Program.cs中,修改Program
类,如下所示
AccessModifiers.程序
using AccessModifiersLibrary;
namespace AccessModifiers
{
class Program
{
static void Main(string[] args)
{
ClassA classA;
}
}
}
并编译代码。
输出
Compile time error: 'AccessModifiersLibrary.ClassA' is inaccessible due to its protection level
我们遇到了这个错误,因为访问限定符internal
意味着我们只能从AccessModifiersLibrary.dll访问ClassA
,而不能从任何其他文件或代码访问。Internal
修饰符意味着访问权限仅限于当前程序。因此,尽量不要创建一个组件并将其类标记为internal
,因为没有人能够使用它。
如果我们从ClassA
中删除internal
字段,代码会编译吗?即,
AccessModifiersLibrary.ClassA
namespace AccessModifiersLibrary
{
class ClassA
{
}
}
AccessModifiers.程序
using AccessModifiersLibrary;
namespace AccessModifiers
{
class Program
{
static void Main(string[] args)
{
ClassA classA;
}
}
}
输出
Compile time error: 'AccessModifiersLibrary.ClassA' is inaccessible due to its protection level
我们又得到了同样的错误。我们不应忘记,如果未指定任何修饰符,类默认是internal
。因此,即使我们不使用任何访问修饰符标记ClassA
,它也默认是internal
,因此编译结果保持不变。
如果类ClassA
被标记为public
,那么一切都会顺利进行,没有任何错误。
要记住的一点:标记为internal
的类只能将其访问权限限制在当前程序集。
带修饰符的命名空间
为了好玩,让我们将AccessModifiers
类库的命名空间在Program
类中标记为public
程序
public namespace AccessModifiers
{
class Program
{
static void Main(string[] args)
{
}
}
}
编译应用程序。
输出
Compile time error: A namespace declaration cannot have modifiers or attributes
要记住的一点:命名空间默认情况下不能有任何访问限定符。它们默认是public
,我们不能添加任何其他访问修饰符,包括再次添加public
。
私有类
我们再做一次实验,将Program
类标记为private
,这样我们的代码就变成了
namespace AccessModifiers
{
private class Program
{
static void Main(string[] args)
{
}
}
}
编译代码。
输出
Compile time error: Elements defined in a namespace cannot be explicitly declared as private, protected, or protected internal
要记住的一点:类只能是public
或internal
。它不能被标记为protected
或private
。类的默认值是internal
。
类成员的访问修饰符
现在有一个重要声明,类的成员可以拥有上述所有解释的访问修饰符,但默认修饰符是private
。
要记住的一点:类的成员可以被所有访问修饰符标记,默认访问修饰符是private
。
如果我们想用两个访问修饰符标记一个方法呢?
namespace AccessModifiers
{
public class Program
{
static void Main(string[] args)
{
}
public private void Method1()
{
}
}
}
编译代码。
输出
Compile time error: More than one protection modifier
因此,我们通常不能用多个访问修饰符标记一个成员。但也有这种情况,我们将在下一节讨论。像int
和object
这样已经定义的类型没有访问限制。它们可以在任何地方使用。
内部类和公共方法
创建一个类库,其中包含一个名为ClassA
的类,标记为internal
,并具有一个public
方法MethodClassA()
,如下所示
namespace AccessModifiersLibrary
{
internal class ClassA
{
public void MethodClassA(){}
}
}
将类库的引用添加到我们的控制台应用程序。现在在控制台应用程序的Program.cs中,尝试访问ClassA
的MethodClassA
方法。
程序
using AccessModifiersLibrary;
namespace AccessModifiers
{
public class Program
{
public static void Main(string[] args)
{
ClassA classA = new ClassA();
classA.MethodClassA();
}
}
}
输出
编译时错误
'AccessModifiersLibrary.ClassA' is inaccessible due to its protection level
The type 'AccessModifiersLibrary.ClassA' has no constructors defined
'AccessModifiersLibrary.ClassA' is inaccessible due to its protection level
'AccessModifiersLibrary.ClassA' does not contain a definition for 'MethodClassA' and
no extension method 'MethodClassA' accepting a first argument of type 'AccessModifiersLibrary.ClassA'
could be found (are you missing a using directive or an assembly reference?)
这么多的错误。不过这些错误都是不言自明的。即使ClassA
的MethodClassA
方法是public
的,由于ClassA
的保护级别,即internal
,它也无法在Program
类中访问。包含MethodClassA
方法的类型是internal
,所以无论该方法是否被标记为public
,我们都无法在任何其他程序集中访问它。
公共类和私有方法
我们将ClassA
类设为public
,方法设为private
AccessModifiersLibrary.ClassA:
namespace AccessModifiersLibrary
{
public class ClassA
{
private void MethodClassA(){}
}
}
程序
using AccessModifiersLibrary;
namespace AccessModifiers
{
public class Program
{
public static void Main(string[] args)
{
ClassA classA = new ClassA();
classA.MethodClassA();
}
}
}
编译输出
'AccessModifiersLibrary.ClassA' does not contain a definition
for 'MethodClassA' and no extension method 'MethodClassA' accepting a first argument
of type 'AccessModifiersLibrary.ClassA' could be found (are you missing a using directive or an assembly reference?)
现在我们将类标记为Public
,但仍然无法访问private
方法。因此,要访问类的成员,类和方法的访问修饰符都非常重要。
注意:本文中的每个代码片段都经过了尝试和测试。
公共类和内部方法
将ClassA
设为public
,MethodClassA
设为internal
AccessModifiersLibrary.ClassA:
namespace AccessModifiersLibrary
{
public class ClassA
{
Internal void MethodClassA(){}
}
}
程序
using AccessModifiersLibrary;
namespace AccessModifiers
{
public class Program
{
public static void Main(string[] args)
{
ClassA classA = new ClassA();
classA.MethodClassA();
}
}
}
编译输出
'AccessModifiersLibrary.ClassA' does not contain a definition for 'MethodClassA' and no extension method 'MethodClassA' accepting a first argument of type 'AccessModifiersLibrary.ClassA' could be found (are you missing a using directive or an assembly reference?)
因此,标记为internal
的成员意味着该DLL之外的任何人都无法访问该成员。
受保护的内部
在类库中,创建三个类ClassA
、ClassB
和ClassC
,并将代码大致放置如下
namespace AccessModifiersLibrary
{
public class ClassA
{
protected internal void MethodClassA()
{
}
}
public class ClassB:ClassA
{
protected internal void MethodClassB()
{
MethodClassA();
}
}
public class ClassC
{
public void MethodClassC()
{
ClassA classA=new ClassA();
classA.MethodClassA();
}
}
}
在我们的控制台应用程序的Program
类中,调用ClassC
的MethodClassC
。
程序
using AccessModifiersLibrary;
namespace AccessModifiers
{
public class Program
{
public static void Main(string[] args)
{
ClassC classC=new ClassC();
classC.MethodClassC();
}
}
}
编译器输出
The code successfully compiles with no error.
Protected internal
修饰符表示两件事,即派生类或同一文件中的类可以访问该方法,因此在上述场景中,派生类ClassB
和同一文件中的类,即ClassC
可以访问标记为protected internal
的ClassA
的方法。
要记住的一点:Protected internal
表示派生类和同一源代码文件中的类可以访问。
受保护成员
在我们的控制台应用程序的Program.cs中,放置以下代码
namespace AccessModifiers
{
class AAA
{
protected int a;
void MethodAAA(AAA aaa,BBB bbb)
{
aaa.a = 100;
bbb.a = 200;
}
}
class BBB:AAA
{
void MethodBBB(AAA aaa, BBB bbb)
{
aaa.a = 100;
bbb.a = 200;
}
}
public class Program
{
public static void Main(string[] args)
{
}
}
}
编译器输出
Cannot access protected member 'AccessModifiers.AAA.a' via a qualifier of type 'AccessModifiers.AAA'; the qualifier must be of type 'AccessModifiers.BBB' (or derived from it)
类AAA
包含一个protected
成员,即a
。但对于同一个类,没有修饰符是有意义的。然而,由于a
是protected
的,在派生类方法MethodBBB
中,我们不能通过AAA
访问它,因为aaa.a
会给我们一个错误。然而,看起来像BBB
的bbb
并没有给出错误。为了验证这一点,注释掉MethodBBB ()
中的行aaa.a=100
。这意味着我们不能从基类的对象访问protected
成员,而只能从派生类的对象访问。尽管a
是基类AAA
的成员,我们仍然不能访问它。同样,我们也不能从Main
方法访问a
。
继承中的可访问性优先级
程序
namespace AccessModifiers
{
class AAA
{
}
public class BBB:AAA
{
}
public class Program
{
public static void Main(string[] args)
{
}
}
}
编译器输出
Compile time error: Inconsistent accessibility: base class 'AccessModifiers.AAA' is less accessible than class 'AccessModifiers.BBB'
这个错误再次给了我们一个需要记住的要点。
要记住的一点:在public
和internal
之间,public
总是允许对其成员进行更大的访问。
类AAA
默认标记为internal
,而从AAA
派生的BBB
则显式标记为public
。我们得到了一个错误,因为派生类BBB
必须具有比基类访问修饰符允许更大访问权限的访问修饰符。这里internal
似乎比public
更具限制性。
但如果我们将这两个类的修饰符颠倒过来,即ClassA
标记为public
,ClassB
标记为internal
或默认,我们就摆脱了错误。
要记住的一点:基类总是允许比派生类更大的可访问性。
另一种情况
程序
namespace AccessModifiers
{
class AAA
{
}
public class BBB
{
public AAA MethodB()
{
AAA aaa= new AAA();
return aaa;
}
}
public class Program
{
public static void Main(string[] args)
{
}
}
}
编译器输出
Inconsistent accessibility: return type 'AccessModifiers.AAA' is less accessible than method 'AccessModifiers.BBB.MethodB()'
这里AAA
的可访问性是internal
,它比public
更具限制性。方法MethodB
的可访问性是public
,它比typeAAA
更大。现在错误发生的原因是方法的返回值必须具有比方法本身更大的可访问性,这在这种情况下是不成立的。
要记住的一点:方法的返回值必须具有比方法本身更大的可访问性。
程序
namespace AccessModifiers
{
class AAA
{
}
public class BBB
{
public AAA aaa;
}
public class Program
{
public static void Main(string[] args)
{
}
}
}
编译器输出
Inconsistent accessibility: field type 'AccessModifiers.AAA' is less accessible than field 'AccessModifiers.BBB.aaa'
图片来源:http://moyamoyya.deviantart.com/art/Rules-147356501
现在规则对每个人都是一样的。类AAA
或数据类型aaa
是internal
。aaa
字段是public
,这使得它比internal
的AAA
更易访问。所以我们得到了错误。
将代码更改为
namespace AccessModifiers
{
class AAA
{
}
public class BBB
{
AAA a;
}
public class Program
{
public static void Main(string[] args)
{
}
}
}
输出编译结果没有错误。
我们学到了很多关于这些访问修饰符的知识,比如public
、private
、protected
、internal
、protected internal
。我们还了解了它们的访问优先级和用法,让我们以表格形式总结它们的详细信息以供复习。稍后,我们还将讨论其他类型。
表格摘自MSDN
声明的可访问性 | 含义 |
public | 访问不受限制。 |
受保护的 | 访问权限仅限于包含类或从包含类派生的类型。 |
internal | 访问权限仅限于当前程序集。 |
受保护的内部 | 访问权限仅限于当前程序集或从包含类派生的类型。 |
私有的 | 访问权限仅限于包含类型。 |
“一个成员或类型只允许一个访问修饰符,除非您使用protected internal
组合。
命名空间不允许访问修饰符。命名空间没有访问限制。
根据成员声明出现的上下文,只允许某些声明的可访问性。如果在成员声明中没有指定访问修饰符,则使用默认可访问性。
顶层类型,即不嵌套在其他类型中的类型,只能具有internal
或public
可访问性。这些类型的默认可访问性是internal
。
嵌套类型,即其他类型的成员,可以具有下表所示的声明可访问性。”
参考:http://msdn.microsoft.com/en-us/library/ba0a1yw2.aspx
成员的 | 默认成员可访问性 | 允许声明的成员可访问性 |
enum | Public | 无 |
类 | Private | public 受保护的 internal 私有的 受保护的内部 |
接口 | Public | 无 |
结构体 | Private | public internal 私有的 |
密封类
“Sealed
”是C#中一种特殊的访问修饰符。如果一个类被标记为sealed
,则其他任何类都不能从该sealed
类派生。换句话说,被标记为sealed
的类不能作为任何其他类的基类。
程序
namespace AccessModifiers
{
sealed class AAA
{
}
class BBB:AAA
{
}
public class Program
{
public static void Main(string[] args)
{
}
}
}
编译器输出
'AccessModifiers.BBB': cannot derive from sealed type 'AccessModifiers.AAA'
故此证明。
要记住的一点:标记为sealed
的类不能作为任何其他类的基类。
图片来源:https://www.flickr.com/photos/lwr/931211869/
访问密封类的成员。
程序
using System;
namespace AccessModifiers
{
sealed class AAA
{
public int x = 100;
public void MethodA()
{
Console.WriteLine("Method A in sealed class");
}
}
public class Program
{
public static void Main(string[] args)
{
AAA aaa=new AAA();
Console.WriteLine(aaa.x);
aaa.MethodA();
Console.ReadKey();
}
}
}
编译器输出
100
Method A in sealed class
因此,正如我们所讨论的,sealed
类和非sealed
类之间唯一的区别是sealed
类不能被派生。sealed
类可以像普通类一样包含变量、方法、属性。
要记住的一点:由于我们不能从sealed
类派生,所以sealed
类中的代码不能被覆盖。
注意:本文中的每个代码片段都经过了尝试和测试。
常量
实验1
我们控制台应用程序中的Program
类。
程序
public class Program
{
private const int x = 100;
public static void Main(string[] args)
{
Console.WriteLine(x);
Console.ReadKey();
}
}
输出
100
我们看到,一个标记为常量的变量或一个const变量在C#中表现得像一个成员变量。我们可以给它一个初始值,并可以在任何地方使用它。
要记住的一点:我们需要在创建const
变量时对其进行初始化。我们不允许在代码或程序中稍后对其进行初始化。
实验2
using System;
namespace AccessModifiers
{
public class Program
{
private const int x = y + 100;
private const int y = z - 10;
private const int z = 300;
public static void Main(string[] args)
{
System.Console.WriteLine("{0} {1} {2}",x,y,z);
Console.ReadKey();
}
}
}
你能猜到输出吗?什么?是编译错误吗?
输出
390 290 300
震惊了吗?一个constant
字段无疑可以依赖于另一个constant
。C#非常聪明,它意识到要计算标记为const
的变量x
的值,它首先需要知道变量y
的值。y
的值取决于另一个const
变量z
,其值设置为300
。因此,C#首先将z
评估为300
,然后y
变为290
(即z - 1
),最后x
取y
的值(即290 + 100
),结果为390
。
实验3
程序
using System;
namespace AccessModifiers
{
public class Program
{
private const int x = y + 100;
private const int y = z - 10;
private const int z = x;
public static void Main(string[] args)
{
System.Console.WriteLine("{0} {1} {2}",x,y,z);
Console.ReadKey();
}
}
}
输出
The evaluation of the constant value for 'AccessModifiers.Program.x' involves a circular definition
我们只是将上一个代码中的z=x
赋值,结果就出现了错误。const x
的值取决于y
,而y
又取决于z
的值,但是我们看到z
的值取决于x
,因为x
直接赋值给了z
,这导致了循环依赖。
要记住的一点:像类一样,const
变量不能是循环的,即它们不能相互依赖。
实验4
一个const
是一个变量,一旦赋值就不能修改,但它的值只在编译时确定。
using System;
namespace AccessModifiers
{
public class Program
{
public const ClassA classA=new ClassA();
public static void Main(string[] args)
{
}
}
public class ClassA
{
}
}
输出
Compile time error: 'AccessModifiers.Program.classA' is of type 'AccessModifiers.ClassA'.
A const field of a reference type other than string can only be initialized with null.
要记住的一点:除string
之外的引用类型的const
字段只能用null
初始化。
如果我们在Program
类中将值赋给null
using System;
namespace AccessModifiers
{
public class Program
{
public const ClassA classA=null;
public static void Main(string[] args)
{
}
}
public class ClassA
{
}
}
然后错误就会消失。当我们现在将classA
初始化为一个在编译时可以确定值的对象,即null
时,错误就消失了。我们永远不能改变classA
的值,所以它将永远是null
。通常,我们不会将const
作为classA
引用类型,因为它们只有在运行时才有值。
要记住的一点:只能将const
变量初始化为编译时值,即在编译器执行时可用的值。
new()
实际上是在运行时执行的,因此在编译时无法获取值。所以这导致了一个错误。
实验5
ClassA
public class ClassA
{
public const int aaa = 10;
}
程序
public class Program
{
public static void Main(string[] args)
{
ClassA classA=new ClassA();
Console.WriteLine(classA.aaa);
Console.ReadKey();
}
}
输出
Compile time error: Member 'AccessModifiers.ClassA.aaa'
cannot be accessed with an instance reference; qualify it with a type name instead
要记住的一点:常量默认是static
的,我们不能使用实例引用,即不能用名称引用const
。const
必须是static
的,因为不允许任何人对const
变量进行任何更改。
只需将const
标记为static
即可。
using System;
namespace AccessModifiers
{
public class ClassA
{
public static const int aaa = 10;
}
public class Program
{
public static void Main(string[] args)
{
ClassA classA=new ClassA();
Console.WriteLine(classA.aaa);
Console.ReadKey();
}
}
}
输出
Compile time error: The constant 'AccessModifiers.ClassA.aaa' cannot be marked static
C#坦率地告诉我们,一个默认已经是static
的字段不能再被标记为static
。
要记住的一点:const
变量不能被标记为static
。
实验6
using System;
namespace AccessModifiers
{
public class ClassA
{
public const int xxx = 10;
}
public class ClassB:ClassA
{
public const int xxx = 100;
}
public class Program
{
public static void Main(string[] args)
{
Console.WriteLine(ClassA.xxx);
Console.WriteLine(ClassB.xxx);
Console.ReadKey();
}
}
}
输出
10
100
Compiler Warning: 'AccessModifiers.ClassB.xxx' hides inherited
member 'AccessModifiers.ClassA.xxx'. Use the new keyword if hiding was intended.
我们总是可以在派生类中创建一个与基类中另一个const
同名的const
。类ClassB xxx
的const
变量将只为类ClassB
隐藏类ClassA
中的const xxx
。
静态字段
要记住的一点:C#中的变量绝不能拥有未初始化的值。
让我们详细讨论一下。
实验1
程序
using System;
namespace AccessModifiers
{
public class Program
{
private static int x;
private static Boolean y;
public static void Main(string[] args)
{
Console.WriteLine(x);
Console.WriteLine(y);
Console.ReadKey();
}
}
}
输出
0
False
要记住的一点:Static
变量总是在类首次加载时初始化。int
被赋予默认值零,bool
被赋予默认值False
。
实验2
程序
using System;
namespace AccessModifiers
{
public class Program
{
private int x;
private Boolean y;
public static void Main(string[] args)
{
Program program=new Program();
Console.WriteLine(program.x);
Console.WriteLine(program.y);
Console.ReadKey();
}
}
}
输出
0
False
要记住的一点:实例变量总是在其实例创建时初始化。
实例变量总是在其实例创建时初始化。关键字new
将创建Program
类的一个实例。它将为每个非static
(即实例)变量分配内存,然后将每个变量初始化为它们的默认值。
实验3
程序
using System;
namespace AccessModifiers
{
public class Program
{
private static int x = y + 10;
private static int y = x + 5;
public static void Main(string[] args)
{
Console.WriteLine(Program.x);
Console.WriteLine(Program.y);
Console.ReadKey();
}
}
}
输出
10
15
输出不言自明。C#总是在创建static
变量后将其初始化为初始值。因此,变量x
和y
被赋予默认值零。C#现在意识到这些声明的变量需要赋值。C#不会一次读取所有行,而是一次只读取一行。它现在将读取第一行,由于变量y
的值为0
,所以x
将得到值10
。然后下一行,y
是x + 5
的值。变量x
的值为10
,因此y
现在变为15
。由于C#不会同时查看两行,因此它不会注意到上述定义的循环性。
实验4
程序
using System;
namespace AccessModifiers
{
public class Program
{
int x = y + 10;
int y = x + 5;
public static void Main(string[] args)
{
}
}
}
输出
Compile time error:
A field initializer cannot reference the non-static field, method, or property 'AccessModifiers.Program.y'
A field initializer cannot reference the non-static field, method, or property 'AccessModifiers.Program.x'
我们在Lab3
中做的实验不适用于实例变量,因为实例变量的规则与static
变量的规则截然不同。实例变量的初始化器必须在实例创建时确定。在此时刻,变量y
没有值。它不能在创建时引用同一对象的变量。所以我们不能引用任何实例成员来初始化一个实例成员。
只读字段
Readonly
字段是C#中OOP最有趣的话题之一。
实验1
程序
using System;
namespace AccessModifiers
{
public class Program
{
public static readonly int x = 100;
public static void Main(string[] args)
{
Console.WriteLine(x);
Console.ReadKey();
}
}
}
输出
100
哇,我们没有得到任何错误,但是请记住不要在static
方法中使用非static
变量,否则我们会得到一个错误。
实验2
程序
using System;
namespace AccessModifiers
{
public class Program
{
public static readonly int x = 100;
public static void Main(string[] args)
{
x = 200;
Console.WriteLine(x);
Console.ReadKey();
}
}
}
输出
Compile time error: A static readonly field cannot be assigned to
(except in a static constructor or a variable initializer).
除了在构造函数中,我们不能更改readonly
字段的值。
要记住的一点:static readonly
字段不能被赋值(除了在static
构造函数或变量初始化器中)。
实验3
程序
using System;
namespace AccessModifiers
{
public class Program
{
public static readonly int x;
public static void Main(string[] args)
{
}
}
}
这里我们发现const
和readonly
之间的一个区别是,与const
不同,readonly
字段不需要在创建时进行初始化。
实验4
程序
using System;
namespace AccessModifiers
{
public class Program
{
public static readonly int x;
static Program()
{
x = 100;
Console.WriteLine("Inside Constructor");
}
public static void Main(string[] args)
{
Console.WriteLine(x);
Console.ReadKey();
}
}
}
输出
Inside Constructor
100
这里可以看到const
和readonly
之间还有一个主要区别。static readonly
变量也可以在构造函数中初始化,就像我们上面提到的例子一样。
实验5
程序
using System;
namespace AccessModifiers
{
public class ClassA
{
}
public class Program
{
public readonly ClassA classA=new ClassA();
public static void Main(string[] args)
{
}
}
}
我们已经在const
部分看到了这个例子。相同的代码在const
中会报错,但在只读字段中则不会。所以我们可以说readonly
是一个更通用的const
,它使我们的程序更具可读性,因为我们引用的是一个名称而不是一个数字。10
更直观还是priceofcookie
更容易理解?为了效率,编译器会将所有const
和readonly
字段转换为实际值。
实验6
程序
using System;
namespace AccessModifiers
{
public class ClassA
{
public int readonly x= 100;
}
public class Program
{
public static void Main(string[] args)
{
}
}
}
输出
Compile time error:
Member modifier 'readonly' must precede the member type and name
Invalid token '=' in class, struct, or interface member declaration
每当我们需要放置多个修饰符时,请提醒自己,有些规则决定了访问修饰符的顺序,哪个在前。现在这里readonly
修饰符在数据类型int
之前,我们已经在文章的开头讨论过了。这只是一个必须始终记住的规则。
实验7
程序
using System;
namespace AccessModifiers
{
public class ClassA
{
public readonly int x= 100;
void Method1(ref int y)
{
}
void Method2()
{
Method1(ref x);
}
}
public class Program
{
public static void Main(string[] args)
{
}
}
}
输出
Compile time error:
A readonly field cannot be passed ref or out (except in a constructor)
A readonly field can’t be changed by anyone except a constructor.
The method Method1 expects a ref parameter which if we have forgotten allows you
to change the value of the original. Therefore C# does not permit a readonly
as a parameter to a method that accepts a ref or an out parameters.
摘要
让我们回顾一下所有需要记住的要点
- 类成员的默认访问修饰符是
private
。 - 标记为
internal
的类只能将其访问权限限制在当前程序集。 - 命名空间默认情况下不能有任何访问限定符。它们默认是
public
,我们不能添加任何其他访问修饰符,包括再次添加public
。 - 类只能是
public
或internal
。它不能被标记为protected
或private
。类的默认值是internal
。 - 类的成员可以被所有访问修饰符标记,默认访问修饰符是
private
。 Protected internal
表示派生类和同一源代码文件中的类可以访问。- 在
public
和internal
之间,public
总是允许对其成员进行更大的访问。 - 基类总是允许比派生类更大的可访问性。
- 方法的返回值必须具有比方法本身更大的可访问性。
- 一个被标记为
sealed
的类不能作为任何其他类的基类。 - 由于我们不能从
sealed
类派生,所以sealed
类中的代码不能被覆盖。 - 我们需要在创建
const
变量时对其进行初始化。我们不允许在代码或程序中稍后对其进行初始化。 - 像类一样,
const
变量不能是循环的,即它们不能相互依赖。 - 除
string
之外的引用类型的const
字段只能用null
初始化。 - 只能将
const
变量初始化为编译时值,即在编译器执行时可用的值。 - 常量默认是
static
的,我们不能使用实例引用,即不能用名称引用const
。const
必须是static
的,因为不允许任何人对const
变量进行任何更改。 const
变量不能被标记为static
。- C#中的变量绝不能拥有未初始化的值。
Static
变量总是在类首次加载时初始化。int
被赋予默认值零,bool
被赋予默认值False
。- 实例变量总是在其实例创建时初始化。
static readonly
字段不能被赋值(除了在static
构造函数或变量初始化器中)。
结论
通过这篇文章,我们几乎完成了所有访问修饰符的场景。我们做了大量的动手实验来澄清我们的概念。我希望我的读者现在能够牢记这些基本概念,并且永远不会忘记它们。在我即将发表的文章,即本系列的最后一篇文章中,我们将讨论C#中的属性和索引器。
继续编码,享受阅读。
另外,如果我的文章对您有任何帮助,请不要忘记评分/评论/点赞,这将激励我,鼓励我写更多文章。
我的其他系列文章
MVC: https://codeproject.org.cn/Articles/620195/Learning-MVC-Part-Introduction-to-MVC-Architectu
RESTful WebAPIs: https://codeproject.org.cn/Articles/990492/RESTful-Day-sharp-Enterprise-Level-Application
祝您编码愉快!