面向初学者的访问修饰符






4.60/5 (30投票s)
在使用访问修饰符之前,请先理解它们。
引言
本文档面向刚开始编程的初学者。我将在此向您展示访问修饰符如何帮助我们设计类,以及何时以及为何使用特定的修饰符。
背景
如果您在互联网上搜索“访问修饰符”,您会找到大量的参考资料。我也做了同样的搜索,并从MSDN摘录了以下内容。
public
: 访问不受限制。private
: 访问仅限于包含的类型。Internal
: 访问仅限于当前程序集。protected
: 访问仅限于包含的类或派生自包含类的类型。protected internal
: 访问仅限于当前程序集或派生自包含类的类型。
Using the Code
那么,让我们通过一个案例研究一步步开始。
- 案例:设计一个
Customer
类,它将具有id
、name
、reward-point
变量。Id
将是一个随机数;对于普通客户,奖励积分将始终为 500,对于 VIP 客户,奖励积分将为 1500。此外,还需要一个print
方法来显示每种客户类型的输出。 - 分析:从需求来看,如果我们进行区分,那么我们会发现需要做的工作是:
Customer
类,其成员为{private Id, public: Name, protected : RewardPoint}
GenaralCustomer
和VIPCustomer
,它们继承Customer
类- 每种客户类型的客户端类,它提供客户姓名并调用
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
类的构造函数中为其赋值。简而言之,我们说“访问仅限于包含的类型”。
Name
是 public
。为什么?因为我们允许从任何地方访问它。它可以从派生类型访问,也可以从同一程序集或不同程序集中的 Customer
实例访问,还可以从同一内容类型访问。简而言之,我们说“访问不受限制”。
Rewardpoint
是 protected
。为什么?因为我们允许从其自己的包含类以及任何程序集中的派生类型访问它,但不能从基类或派生类型的实例访问。用一个词来说,我们说“访问仅限于包含的类*或派生自包含类的类型。”
PrintCustomerInfo()
是 internal
。为什么?因为我们允许在同一程序集中访问,以便与 Customer
类在同一程序集中的任何继承 Customer
的类都可以重写该方法。简而言之,我们说“访问仅限于当前程序集”。
相同程序集和不同程序集是什么意思?
请看下面的图片。这里我的 Customer
类和 Generalcustomer
类在同一个程序集中。这两个类都在名为 SameAssembly
的同一个类库中,并且共享相同的命名空间。所以我们可以说它们在同一个程序集中。ClientForVip
和 VipCustomer
在同一个命名空间/类库中。如果 Customer
类在 VipCustomer
类中使用,那么我们可以说它使用了不同的程序集。
请注意,Customer
类的 rewardpoint
和 PrintCustomerInfo
成员都可以轻松地从 GeneralCustomer
类中访问。rewardPoint
是 protected
,因此可以轻松地从派生类型访问;PrintCustomerInfo
是 internal
,因此 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
中引发另一个问题,因为如果 PrintCustomerInfo
是 protected
,我们将无法从那里访问它。下面的行将导致错误:
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();
}
}
}
如果我们向任何控制台应用程序添加 SameAssembly
和 DifferentAssembly
的引用,并像下面这样调用每种类型的客户端:
namespace AccessModifiers
{
class Program
{
static void Main(string[] args)
{
var generalClient = new ClientForGeneral();
generalClient.Print();
var vipClient = new ClientForVip();
vipClient.Print();
Console.ReadKey();
}
}
}
它会产生以下输出:
我发现大多数开发者对 internal
和 protected internal
的用法感到困惑。希望本文能帮助他们理清对访问修饰符的理解。