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

面向初学者的访问修饰符

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.60/5 (30投票s)

2013年7月20日

CPOL

5分钟阅读

viewsIcon

42069

在使用访问修饰符之前,请先理解它们。

引言

本文档面向刚开始编程的初学者。我将在此向您展示访问修饰符如何帮助我们设计类,以及何时以及为何使用特定的修饰符。

背景

如果您在互联网上搜索“访问修饰符”,您会找到大量的参考资料。我也做了同样的搜索,并从MSDN摘录了以下内容。

  • public: 访问不受限制。
  • private: 访问仅限于包含的类型。
  • Internal: 访问仅限于当前程序集。
  • protected: 访问仅限于包含的类或派生自包含类的类型。
  • protected internal: 访问仅限于当前程序集或派生自包含类的类型。

Using the Code

那么,让我们通过一个案例研究一步步开始。

  • 案例:设计一个 Customer 类,它将具有 idnamereward-point 变量。Id 将是一个随机数;对于普通客户,奖励积分将始终为 500,对于 VIP 客户,奖励积分将为 1500。此外,还需要一个 print 方法来显示每种客户类型的输出。
  • 分析:从需求来看,如果我们进行区分,那么我们会发现需要做的工作是:
  1. Customer 类,其成员为 {private Id, public: Name, protected : RewardPoint}
  2. GenaralCustomerVIPCustomer,它们继承 Customer
  3. 每种客户类型的客户端类,它提供客户姓名并调用 print 方法。

下面的代码是用于分析 #1

namespace SameAssembly
{
    public class Customer
    {
        private int Id {get;set;} /*private : Access is limited to the containing type.*/
        public string Name {get;set;}/*public : Access is not restricted.*/
        protected int RewardPoint{get;set;}/*protected : 
        			*Access is limited to the containing class 
                    * or types derived from the containing class.*/
        public Customer()
        {
            Id = new Random().Next();
        }

        /*internal :Access is limited to the current assembly or types. */
        internal virtual void PrintCustomerInfo()  
        {
            Console.WriteLine("\t\t\t\t ID:{0} \n\n \t\t\t\t 
            Name:{1} \n\n \t\t\t\t RewardPoint:{2} \n\n", Id, Name, RewardPoint);
        }
    }
}

下面的代码是用于分析 #2,针对 GeneralCustomer

namespace SameAssembly
{
    public class GeneralCustomer : Customer
    {
        internal string CustomerType
        {
            get { return "******** FOR GENERAL CUSTOMER ****************"; }
        }
        public GeneralCustomer()
        {
            RewardPoint = 500;
        }

        internal override void PrintCustomerInfo()
        {
            Console.WriteLine("\n\n\t\t\t" + CustomerType + "\n\n\t\t\t");
            base.PrintCustomerInfo();
        }
    }
} 

下面的代码是用于分析 #3,针对 GeneralCustomer 的客户端

namespace SameAssembly
{
    public class ClientForGeneral
    {
        public void Print()
        {
            var generalCustomer = new GeneralCustomer();
            generalCustomer.Name = "Mr Jhon";
            generalCustomer.PrintCustomerInfo();
        }
    }
}

如果您查看上面的 Customer 类,您会发现 ID 是 private。为什么?因为我们不允许任何其他类访问它,甚至不能从 Customer 类的实例访问它,我们只能在声明该类的 Customer 类主体中访问它。因此,我们在 Customer 类的构造函数中为其赋值。简而言之,我们说“访问仅限于包含的类型”。

Namepublic。为什么?因为我们允许从任何地方访问它。它可以从派生类型访问,也可以从同一程序集或不同程序集中的 Customer 实例访问,还可以从同一内容类型访问。简而言之,我们说“访问不受限制”。

Rewardpointprotected。为什么?因为我们允许从其自己的包含类以及任何程序集中的派生类型访问它,但不能从基类或派生类型的实例访问。用一个词来说,我们说“访问仅限于包含的类*或派生自包含类的类型。

PrintCustomerInfo()internal。为什么?因为我们允许在同一程序集中访问,以便与 Customer 类在同一程序集中的任何继承 Customer 的类都可以重写该方法。简而言之,我们说“访问仅限于当前程序集”。

相同程序集和不同程序集是什么意思?

请看下面的图片。这里我的 Customer 类和 Generalcustomer 类在同一个程序集中。这两个类都在名为 SameAssembly 的同一个类库中,并且共享相同的命名空间。所以我们可以说它们在同一个程序集中。ClientForVipVipCustomer 在同一个命名空间/类库中。如果 Customer 类在 VipCustomer 类中使用,那么我们可以说它使用了不同的程序集。

请注意,Customer 类的 rewardpointPrintCustomerInfo 成员都可以轻松地从 GeneralCustomer 类中访问。rewardPointprotected,因此可以轻松地从派生类型访问;PrintCustomerInfointernal,因此 GeneralCustomer 可以访问它,因为它在同一个程序集中。

下面的代码是用于分析 #2,针对 VIPlCustomer

代码是:

namespace DifferentAssembly
{
    public class VipCustomer : Customer
    {
        internal string CustomerType
        {
            get
            {
                return "******** FOR VIP CUSTOMER ****************";
            }
        }
        public VipCustomer()
        {
            RewardPoint = 1500;
        }
        internal  override void PrintCustomerInfo()
        {
            Console.WriteLine("\n\n\t\t\t" + CustomerType+ "\n\n\t\t\t");
            base.PrintCustomerInfo();
        }        
    }
}

这里是关键。我将 VIPCustomer 类放在与 Customer 类不同的类库中。从那里,我想在派生类 VIPCustomer 中访问我的 Customer 类成员。那么上面的代码会编译通过吗?不,它会给出以下编译错误:

Error 1 'DifferentAssembly.VipCustomer.PrintCustomerInfo()': 
no suitable method found to override 
J:\Access Modifiers\AccessModifiers\DifferentAssembly\VipCustomer.cs 18 33 DifferentAssembly 

为什么?因为 rewardpoint 没有问题,可以从任何派生类型访问,因为它被定义为 protected。但是,由于 PrintCustomerInfo 被定义为 internal,因此无法从另一个程序集访问它。正如我之前所说:

PrintCustomerInfo() 是 internal。为什么?因为我们允许在同一程序集中访问,以便与 customer 类在同一程序集中的任何继承 customer 类的类都可以重写该方法。

那么解决方案是什么?我们将其设置为 protected,这能解决我们的问题吗?不,完全不能。它会在我们的客户端类 ClientForGeneral 中引发另一个问题,因为如果 PrintCustomerInfoprotected,我们将无法从那里访问它。下面的行将导致错误:

generalCustomer.PrintCustomerInfo();  

那么解决方案是什么?解决方案是 protected internal。我们需要像下面这样更改 Customer 类的 PrntCustomerInfo 方法:

/*protected internal :Access is limited to the current assembly or types 
 * derived from the containing class. */
protected internal virtual void PrintCustomerInfo()  
{
    Console.WriteLine("\t\t\t\t ID:{0} \n\n \t\t\t\t 
    Name:{1} \n\n \t\t\t\t RewardPoint:{2} \n\n", Id, Name, RewardPoint);
}

在当前程序集中,客户端 ClientForGeneral 通过对象访问 PrintCustomerInfo 将没有任何问题,因为它在同一个程序集中;在这里,它将表现为 internal

因此,GeneralCustomerClass 中的重写方法将如下所示:

protected  internal override void PrintCustomerInfo()
{
    Console.WriteLine("\n\n\t\t\t" + CustomerType+ "\n\n\t\t\t");
    base.PrintCustomerInfo();
}

VipCustomer 中,它将表现为 protected,如下所示:这里不能使用 protected internal;只有 protected 修饰符在这里有效。

protected  override void PrintCustomerInfo()
{
    Console.WriteLine("\n\n\t\t\t" + CustomerType+ "\n\n\t\t\t");
    base.PrintCustomerInfo();
} 

如果将其编写为 protected internal,您将收到编译错误:

Error 1 'DifferentAssembly.VipCustomer.PrintCustomerInfo()': cannot change access
 modifiers when overriding 'protected' inherited member
 'SameAssembly.Customer.PrintCustomerInfo()' 
J:\Access Modifiers\AccessModifiers\DifferentAssembly\VipCustomer.cs 17 42 DifferentAssembly

因此,编译器会轻松地假定派生类型,它将表现为 protected 而不是 protected internal

关注点

客户端类 ClientForVip 怎么办?它如何访问 PrintCustomerInfo() 方法,因为它现在是 protected?我们需要付出额外的努力,我们需要另一个可以通过该类实例访问的方法。

internal void PrintVipCustomerInfo()
{
    PrintCustomerInfo();
} 

现在客户端类可以通过其内部方法 PrintVipCustomerInfo 访问 PrintCustomerInfo

namespace DifferentAssembly
{
    class ClientForVip
    {
        public void Print()
        {
            var generalCustomer = new VipCustomer();
            generalCustomer.Name= "Mr Mark";
            generalCustomer.PrintVipCustomerInfo();
        }
    }
} 

如果我们向任何控制台应用程序添加 SameAssemblyDifferentAssembly 的引用,并像下面这样调用每种类型的客户端:

namespace AccessModifiers
{
    class Program
    {
        static void Main(string[] args)
        {
            var generalClient = new ClientForGeneral();
            generalClient.Print();
            
            var vipClient = new ClientForVip();            
            vipClient.Print();
            Console.ReadKey();
        }
    }
}

它会产生以下输出:

我发现大多数开发者对 internalprotected internal 的用法感到困惑。希望本文能帮助他们理清对访问修饰符的理解。

© . All rights reserved.