65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.90/5 (152投票s)

2014年7月3日

CPOL

21分钟阅读

viewsIcon

405555

downloadIcon

8090

访问修饰符(或访问限定符)是面向对象语言中的关键字,用于设置类、方法和其他成员的访问权限。访问修饰符是编程语言语法的一个特定部分,用于促进组件的封装。

引言

感谢我的读者们的大力支持,这激励我继续撰写本OOP系列文章。

我们已经涵盖了C#中继承和多态的几乎所有方面。我的文章将重点介绍C#中访问修饰符的几乎所有方面/场景。我们将通过动手实验而不仅仅是理论来学习。我们将以“实验”的形式对章节进行分类,以一种非常不同的方式涵盖我最喜欢的主题——常量。我在这篇文章中的努力将是涵盖相关主题的每一个概念,以便在文章结束时,我们可以自信地说我们“了解C#中访问修饰符的一切”。赶紧投入到OOP中吧。

图片来源:http://en.wikipedia.org/wiki/Colocation_centre

先决条件

我期望本文的读者对C#有非常基本的了解。读者只需了解访问修饰符的定义即可。最后但同样重要的是,一如既往,我希望我的读者能够享受阅读本文的过程。

路线图

让我们回顾一下我们的路线图

                                                   

  1. 深入OOP(第1天):多态性和继承(早期绑定/编译时多态)
  2. 深入OOP(第2天):多态性和继承(继承)
  3. 深入OOP(第3天):多态性和继承(动态绑定/运行时多态)
  4. 深入OOP(第4天):多态性和继承(C#中抽象类的一切)
  5. 深入OOP(第5天):C#中访问修饰符的一切(Public/Private/Protected/Internal/Sealed/Constants/Readonly字段)
  6. 深入OOP(第6天):理解C#中的Enum(一种实用方法)
  7. 深入OOP(第7天):C#中的属性(一种实用方法)
  8. 深入OOP(第8天):C#中的索引器(一种实用方法)
  9. 深入OOP(第9天):理解C#中的事件(洞察)
  10. 学习C#(第10天):C#中的委托(一种实用方法)
  11. 学习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方法AAABBB。方法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

因此,由于方法AAAprivate的,除了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

要记住的一点:类只能是publicinternal。它不能被标记为protectedprivate。类的默认值是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

因此,我们通常不能用多个访问修饰符标记一个成员。但也有这种情况,我们将在下一节讨论。像intobject这样已经定义的类型没有访问限制。它们可以在任何地方使用。

内部类和公共方法

创建一个类库,其中包含一个名为ClassA的类,标记为internal,并具有一个public方法MethodClassA(),如下所示

namespace AccessModifiersLibrary
{
    internal class ClassA
    {
        public void MethodClassA(){}
    }
}

将类库的引用添加到我们的控制台应用程序。现在在控制台应用程序的Program.cs中,尝试访问ClassAMethodClassA方法。

程序

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?)

这么多的错误。不过这些错误都是不言自明的。即使ClassAMethodClassA方法是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设为publicMethodClassA设为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之外的任何人都无法访问该成员。

受保护的内部

在类库中,创建三个类ClassAClassBClassC,并将代码大致放置如下

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类中,调用ClassCMethodClassC

程序

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 internalClassA的方法。

要记住的一点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。但对于同一个类,没有修饰符是有意义的。然而,由于aprotected的,在派生类方法MethodBBB中,我们不能通过AAA访问它,因为aaa.a会给我们一个错误。然而,看起来像BBBbbb并没有给出错误。为了验证这一点,注释掉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'

这个错误再次给了我们一个需要记住的要点。

要记住的一点:在publicinternal之间,public总是允许对其成员进行更大的访问。

AAA默认标记为internal,而从AAA派生的BBB则显式标记为public。我们得到了一个错误,因为派生类BBB必须具有比基类访问修饰符允许更大访问权限的访问修饰符。这里internal似乎比public更具限制性。

但如果我们将这两个类的修饰符颠倒过来,即ClassA标记为publicClassB标记为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或数据类型aaainternalaaa字段是public,这使得它比internalAAA更易访问。所以我们得到了错误。

将代码更改为

 namespace AccessModifiers
{
    class AAA
    {

    }
    public class BBB
    {
         AAA a;
    }
    public class Program
    {
        public static void Main(string[] args)
        {
        }
    }
}

输出编译结果没有错误。

我们学到了很多关于这些访问修饰符的知识,比如publicprivateprotectedinternalprotected internal。我们还了解了它们的访问优先级和用法,让我们以表格形式总结它们的详细信息以供复习。稍后,我们还将讨论其他类型。

表格摘自MSDN

声明的可访问性含义
public访问不受限制。
受保护的访问权限仅限于包含类或从包含类派生的类型。
internal访问权限仅限于当前程序集。
受保护的内部访问权限仅限于当前程序集或从包含类派生的类型。
私有的访问权限仅限于包含类型。

“一个成员或类型只允许一个访问修饰符,除非您使用protected internal组合。

命名空间不允许访问修饰符。命名空间没有访问限制。

根据成员声明出现的上下文,只允许某些声明的可访问性。如果在成员声明中没有指定访问修饰符,则使用默认可访问性。

顶层类型,即不嵌套在其他类型中的类型,只能具有internalpublic可访问性。这些类型的默认可访问性是internal

嵌套类型,即其他类型的成员,可以具有下表所示的声明可访问性。”

参考http://msdn.microsoft.com/en-us/library/ba0a1yw2.aspx

成员的默认成员可访问性允许声明的成员可访问性
enumPublic
Privatepublic
受保护的
internal
私有的
受保护的内部
接口Public
结构体Privatepublic
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),最后xy的值(即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的,我们不能使用实例引用,即不能用名称引用constconst必须是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 xxxconst变量将只为类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变量后将其初始化为初始值。因此,变量xy被赋予默认值零。C#现在意识到这些声明的变量需要赋值。C#不会一次读取所有行,而是一次只读取一行。它现在将读取第一行,由于变量y的值为0,所以x将得到值10。然后下一行,yx + 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)
        {
        }
    }
}

这里我们发现constreadonly之间的一个区别是,与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

这里可以看到constreadonly之间还有一个主要区别。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更容易理解?为了效率,编译器会将所有constreadonly字段转换为实际值。

实验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.

摘要

让我们回顾一下所有需要记住的要点

  1. 类成员的默认访问修饰符是private
  2. 标记为internal的类只能将其访问权限限制在当前程序集。
  3. 命名空间默认情况下不能有任何访问限定符。它们默认是public,我们不能添加任何其他访问修饰符,包括再次添加public
  4. 类只能是publicinternal。它不能被标记为protectedprivate。类的默认值是internal
  5. 类的成员可以被所有访问修饰符标记,默认访问修饰符是private
  6. Protected internal表示派生类和同一源代码文件中的类可以访问。
  7. publicinternal之间,public总是允许对其成员进行更大的访问。
  8. 基类总是允许比派生类更大的可访问性。
  9. 方法的返回值必须具有比方法本身更大的可访问性。
  10. 一个被标记为sealed的类不能作为任何其他类的基类。
  11. 由于我们不能从sealed类派生,所以sealed类中的代码不能被覆盖。
  12. 我们需要在创建const变量时对其进行初始化。我们不允许在代码或程序中稍后对其进行初始化。
  13. 像类一样,const变量不能是循环的,即它们不能相互依赖。
  14. string之外的引用类型的const字段只能用null初始化。
  15. 只能将const变量初始化为编译时值,即在编译器执行时可用的值。
  16. 常量默认是static的,我们不能使用实例引用,即不能用名称引用constconst必须是static的,因为不允许任何人对const变量进行任何更改。
  17. const变量不能被标记为static
  18. C#中的变量绝不能拥有未初始化的值。
  19. Static变量总是在类首次加载时初始化。int被赋予默认值零,bool被赋予默认值False
  20. 实例变量总是在其实例创建时初始化。
  21. static readonly字段不能被赋值(除了在static构造函数或变量初始化器中)。

结论

通过这篇文章,我们几乎完成了所有访问修饰符的场景。我们做了大量的动手实验来澄清我们的概念。我希望我的读者现在能够牢记这些基本概念,并且永远不会忘记它们。在我即将发表的文章,即本系列的最后一篇文章中,我们将讨论C#中的属性和索引器。

继续编码,享受阅读。

另外,如果我的文章对您有任何帮助,请不要忘记评分/评论/点赞,这将激励我,鼓励我写更多文章。

我的其他系列文章

MVChttps://codeproject.org.cn/Articles/620195/Learning-MVC-Part-Introduction-to-MVC-Architectu

RESTful WebAPIshttps://codeproject.org.cn/Articles/990492/RESTful-Day-sharp-Enterprise-Level-Application

祝您编码愉快!

© . All rights reserved.