去年12月份,随着Visual Studio 2017 Update 15.5的发布,Visual C#迎来了它的最新版本:7.2. 在这个版本中,有个让人难以理解的新特性,就是private protected访问修饰符(Access Modifier)。至此,C#语言的访问修饰符有以下几种:

  • private
  • protected
  • public
  • internal
  • internal protected
  • private protected

既然有了private和protected,那么private protected又是什么?它跟internal protected又有什么关系?本文简单介绍一下。

private protected是怎么回事

在解释private protected之前,首先让我们回顾一下internal protected访问修饰符。internal protected表示,相同程序集(Assembly)中的其它类型,或者当前类的子类,具有访问该类中internal protected成员的能力,可以用下图表示:

在上图中:

  • 程序集A中的X类,可以访问A类中的Method方法
  • 程序集A中的B类,可以重载A类中的Method方法,B类中的其它成员也可以访问A类中的Method
  • 程序集B中的C类,可以重载A类中的Method方法,C类中的其它成员也可以通过base.Method()访问A类中的Method
  • 程序集B中C类的Method方法重载了A类的Method方法,因此,internal关键字被去掉,于是,程序集B中的Y类,无法访问C类中的Method方法

因此,internal protected表示internal或者protected。

然而,private protected表示,仅有相同程序集(Assembly)中继承于当前类型的类,才能访问该类中private protected成员。换句话说,private protected就是访问者必须在相同程序集中(internal),同时还必须是被访问类型的子类(protected),可以用下图表示:

因此,private protected表示internal并且protected。

private protected何时使用

理论上讲,private protected完善了C#语言的封装性,提供了另一层级别的成员访问保护,听起来感觉让人费解又没什么用。那么,什么时候使用这个访问修饰符呢?现假设你正在设计一个框架,其中有个类,它提供对象存储功能,它的职责是保存给定的对象,而它的每一种实现都需要依赖于一个对象的序列化机制,比如:

public sealed class SerializationHelper
{
public string Serialze(object s)
{
using (var memoryStream = new MemoryStream())
{
var serializer = new XmlSerializer(s.GetType());
serializer.Serialize(memoryStream, s);
return Encoding.UTF8.GetString(memoryStream.ToArray());
}
}
} public abstract class DataStorage
{
private readonly SerializationHelper serializer = new SerializationHelper();
protected SerializationHelper Serializer => serializer;
protected abstract void SaveObject(object obj);
} public sealed class InMemoryDataStorage : DataStorage
{
private readonly List<string> serializedData = new List<string>();
protected override void SaveObject(object obj)
=> serializedData.Add(Serializer.Serialze(obj));
}

上面的代码中,SerializationHelper提供了一种将对象序列化成XML字符串的机制;DataStorage是所有对象数据存储的基类,它当然也为其子类提供了一个访问对象序列化器的方式。由于这个对象序列化器是提供给其子类调用的,因此,DataStorage中的Serializer属性是protected的。最后,InMemoryDataStorage继承了DataStorage,通过调用由基类提供的Serializer属性,实现了SaveObject方法。

整个实现当然没有问题。可是,通过审核所有类型的可见性,我们发现,我们不打算将SerializationHelper这个类暴露给外界,也就是不希望其它的程序集能够直接访问SerializationHelper类,于是,我们将它设置成internal的。也就是:

internal sealed class SerializationHelper
{
public string Serialze(object s)
{
using (var memoryStream = new MemoryStream())
{
var serializer = new XmlSerializer(s.GetType());
serializer.Serialize(memoryStream, s);
return Encoding.UTF8.GetString(memoryStream.ToArray());
}
}
}

好了,问题来了,编译器开始抱怨了,说SerializationHelper类的访问级别比DataStorage.Serializer属性的访问级别要低:

道理显而易见:DataStorage.Serializer属性在DataStorage的子类中即可访问,这个子类可以是在DataStorage所在的程序集中,也可以是在另一个程序集中。然而,这个属性的依赖类型:SerializationHelper类,却只能在DataStorage所在的程序集中才能被访问。

于是,能量巨大的private protected闪亮登场。将DataStorage.Serializer属性的访问修饰符从protected改为private protected,问题就解决了:

internal sealed class SerializationHelper
{
public string Serialze(object s)
{
using (var memoryStream = new MemoryStream())
{
var serializer = new XmlSerializer(s.GetType());
serializer.Serialize(memoryStream, s);
return Encoding.UTF8.GetString(memoryStream.ToArray());
}
}
} public abstract class DataStorage
{
private readonly SerializationHelper serializer = new SerializationHelper();
private protected SerializationHelper Serializer => serializer;
protected abstract void SaveObject(object obj);
} public sealed class InMemoryDataStorage : DataStorage
{
private readonly List<string> serializedData = new List<string>();
protected override void SaveObject(object obj)
=> serializedData.Add(Serializer.Serialze(obj));
}

不过,一旦使用了private protected访问修饰符,DataStorage.Serializer属性就只能在DataStorage所在的程序集的子类中访问了。

private protected如何使用

private protected访问修饰符是C# 7.2的新特性。自从使用Roslyn编译器服务的C# 6.0开始,C#编译器的版本更新就可以与.NET Framework和Visual Studio的发布分离开来了。这一点在C# 7.x(包括7.1和7.2)的发布中逐步显现出来。在Visual Studio 2017的编译高级选项中,开发人员可以很方便地选择所需的C#版本:

如上图所述,在C#项目上点右键,在项目属性的Build标签页中,点击Advanced按钮,在Advanced Build Settings对话框中,通过Language version下拉框来选择所需的C#语言版本。其中:

  • C# latest major version (default):C#最新的主版本,也就是与Visual Studio一起发布的主要版本,在VS2017上对应C# 7.0
  • C# latest minor version (latest):C#的最新版,通常通过VS2017的升级包获得

要使用private protected访问修饰符,则需要在此选择C# latest minor version (latest),或者C# 7.2.

总结

本文对C# 7.2的新特性:private protected访问修饰符进行了解析,并通过案例来说明它的应用场景以及Visual Studio 2017对于C#新特性的支持。

解析Visual C# 7.2中的private protected访问修饰符的更多相关文章

  1. [译]C# 7系列,Part 5: private protected 访问修饰符

    原文:https://blogs.msdn.microsoft.com/mazhou/2017/10/05/c-7-series-part-5-private-protected/ C#有几个可访问性 ...

  2. 关于public private protected访问修饰符

    这个似乎都是老生常谈了,特别是找工作第一轮笔试的时候很爱考这些,再罗列一次,特别要注意继承的情况:     默认状态:即是不加修饰符的时候,所谓的default状态,在类内部可以被访问,在相同的包下面 ...

  3. Java 中的 protected 访问修饰符你真的了解吗?

    protected Java 中的 protected 访问修饰符 总结 在同一个包中,类中 protected 或 default 修饰的属性或方法可以在类外被其对象 (实例) 外部访问,也可以被子 ...

  4. C#中的五个访问修饰符

    一.public, private, protected, internal, protected internal 1.public : 公开的, 公共的 2.private : 私有的 (只能在当 ...

  5. 简述public private protected internal修饰符的访问权限

    public 关键字是类型和类型成员的访问修饰符.公共访问是允许的最高访问级别.对访问公共成员没有限制. protected 关键字是一个成员访问修饰符.受保护成员在它的类中可访问并且可由派生类访问. ...

  6. 什么是封装? ----------------php中"public"类似的访问修饰符分别有什么作用?----什么是抽象?抽象的关键字以及用法----- 什么是接口?接口的关键字以及用法-------------

    什么是封装? ------------------------------------封装是php面向对象的其中一个特性,将多个可重复使用的函数封装到一个类里面.在使用时直接实例化该类的某一个方法,获 ...

  7. Java中的protected访问修饰符

    在某个类中定义的protected 方法和属性和默认权限方法和属性是一样的.比如,某类的protected 方法和属性在包外是不能通过该类实例进行访问的(你能在包外访问一个类的默认权限的方法和属性吗? ...

  8. 深入浅出OOP(五): C#访问修饰符(Public/Private/Protected/Internal/Sealed/Constants)

    访问修饰符(或者叫访问控制符)是面向对象语言的特性之一,用于对类.类成员函数.类成员变量进行访问控制.同时,访问控制符也是语法保留关键字,用于封装组件. Public, Private, Protec ...

  9. java中抽象类与接口中方法访问修饰符问题 (

    1.抽象类中的抽象方法(其前有abstract修饰)不能用private.static.synchronized.native访问修饰符修饰.原 因如下:抽象方法没有方法体,是用来被继承的,所以不能用 ...

随机推荐

  1. centos7 yum与Python3冲突

    虽然标题不严谨,但是,我自己尝试了多次,在安装了Python3.6后,yum无法同步Python.所以采用网上抄来抄去的做法,将yum指定使用Python2,凡是需要安装与Python相关的模块,使用 ...

  2. python技巧

    python小技巧: 1.强烈建议使用Python的r前缀,就不用考虑转义的问题了. 2.正则表达式的使用: test = '用户输入的字符串' if re.match(r'正则表达式', test) ...

  3. postman中 form-data、x-www-form-urlencoded、raw、binary的区别

    1.form-data:  就是http请求中的multipart/form-data,它会将表单的数据处理为一条消息,以标签为单元,用分隔符分开.既可以上传键值对,也可以上传文件.当上传的字段是文件 ...

  4. shell 批量创建带随机字符串的html文件

    思路一: echo $RANDOM|md5sum |tr "[0-9]" "[a-j]"|cut -c 2-11 RANDOM:  linux内置的随机数变量 ...

  5. 使用tcpcopy导入线上流量进行功能和压力测试

    - 假设我们要上线一个两年内不会宕机的先进架构.在上线前,免不了单元测试,功能测试,还有使用ab,webbench等等进行压力测试. 但这些步骤非生产环境下正式用户的行为.或许你会想到灰度上线,但毕竟 ...

  6. gm8180:arm linux启动加载模块、运行程序

    1. init #!/bin/busybox ash#load modules mao 2013-02-16 14:12:48 echo "************************m ...

  7. HI3531的DDR3配置流程

    DDR3 初始化配置流程 系统上电之后,必须先完成DDR3 SDRAM 的初始化操作,系统才能访问DDR3 SDRAM.在进行初始化之前需要注意以下几点: 对DDR3 SDRAM 进行上电操作时,需要 ...

  8. 【linux】linux下网络的配置

    linux网络的配置 一.配置网络vi  /etc/sysconfig/network 配置网络vi /etc/sysconfig/network-scripts/ifcfg-eth0 DEVICE= ...

  9. strcpy函数

    不调用C/C++库函数,编写strcpy()函数. char * my_strcpy(char *strDest,const char *strSrc) { char *p=strDest; whil ...

  10. Java中的大小写字母相互转换(不利用Java自带的方法)

    Java中的大小写字母相互转换(不利用Java自带的方法) 1.设计源码 /** * * @title:UpperAndLower.java * @Package:com.you.utils * @D ...