【.Net底层剖析】3.用IL来理解属性

简介: 【.Net底层剖析】3.用IL来理解属性

阅读目录

 

.Net底层剖析目录章节

1.【深入浅出.Net IL】1.一个For循环引发的IL

2.【.Net底层剖析】2.stfld指令-给对象的字段赋值

3.【.Net底层剖析】3.用IL来理解属性

未完待续......

概述:

我们经常在code中用到属性,但是我们真的知道属性和字段的区别吗?为什么会有属性这个用法?带着这两个问题,我们来用IL中间语言剖析一下属性(Property

 

C#中如何定义一个属性

public string name { get; set; }

  图片.png

 

编译之后,用ildasm.exe工具看下IL代码

ildasm.exe 路径:C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools 不同的机器,ildasm.exe路径可能不同

图片.png

在我们分析IL代码之前,我们需要了解下IL的符号代表的含义:

图片.png

Student

1.查看更多信息

图片.png

双击进去就可以看到详细的IL代码:

图片.png

  1. .class 表明了Student是一个类。该类继承自外部程序集mscorlibSystem.Object类,且Student作为另一个类的子类
  2. auto表明程序加载时内存的布局是由CLR决定的,而不是程序本身。
  3. ansi标识为了实现托管代码和非托管代码间的无缝转换。
  4. public为访问控制权限。
  5. Beforefieldinit 属性为Student提供了一个附加信息,用于标记运行库可以在任何时候执行类型构造函数方法,只要该构造方法在第一次访问其静态字段之前执行即可。如果没有beforefieldinit则运行库必须在某个精确时间执行类型构造函数方法,从而影响性能优化。
  6. extends 继承
  7. [mscorlib]多语言标准通用对象运行时库

2.构造函数

图片.png

双击进去查看IL代码

图片.png

  1. .method 表明.ctor为一个方法
  2. public访问权限
  3. hidebysig属性用于表示表示如果当前Student类作为父类时,类中的标记了hidebysig的方法不会被子类继承,因此该构造函数不会被继承
  4. specialname MSDN英文解释Method is special.  Name describes how. 方法是特殊的,名字描述是怎样特殊。
  5. rtspecialname MSDN英文解释Runtime should check name encoding. 运行时应该检查名字的编码
  6. void .ctor() 返回值为void的无参方法.ctor
  7. cil managed 说明方法体中为IL代码,指示编译器编译为托管代码
  8. .maxstack表明执行构造函数.ctor期间的评估堆栈(Evaluatuion Stack)可容纳数据项的最大个数。评估堆栈:用于保存方法所需变量的值,并在方法执行结束时清空,或者存储一个返回值。
  9. IL_xxxx 标记代码行,一般来说,IL标记之前的部分为变量的声明和初始化。
  10. Ldarg.0(load argument)装载第一个成员参数,在实例方法中指的是当前实例的引用,该实例引用将用于在基类构造函数中调用。
  11. call instance void [mscorlib]System.Object::.ctor()调用Student的父类Object的构造函数.ctor()
  12. ret(return)方法返回

回到顶部

属性Name

1.隐藏字段

图片.png

私有字段<Name>K__BackingFieldstring类型,

2.get_Name方法

图片.png

ldarg要特别注意一个问题:如果是实例方法的话ldarg.0加载的是本身,也就是this,ldarg.1加载的才是函数的第一个参数;如果是静态函数,ldarg.0就是第一个参数。

图片.png

所以get_Name的作用就是得到隐藏字段<Name>k__BackingField的值

s.Name相当于得到了隐藏字段<Name>k__BackingField的值

 

3.set_Name方法

图片.png

所以set_Name的作用就是将set_Name中的参数value赋值给隐藏字段<Name>k__BackingField

s.Name = "Jackson" 相当于value="Jackson",value赋值给<Name>k__BackingField

4.属性Name

图片.png

Main方法

图片.png

newobj指令:

  1. 从托管堆分配指定类型所需要的全部内存空间。
  2. 从调用执行构造函数初始化之前,首先初始化对象的附加成员:
  1. 指向该类型方法表的指针
  2. SyncBlockIndex,用于进行线程同步。

    所有的对象都包含这两个附加成员,用于管理对象。

  1. 调用构造函数ctor,进行初始化操作,并返回新建对象的引用地址,将引用地址加入Execution Stack

所以s.Name就是调用set_Name(string value)方法,将"Jackson"的值传给set_NamesetName方法中将value的值赋值给隐藏字段<Name>k__BackingField

实现get,set方法

上面的Name属性是一个自动实现的属性

下面我们来显示实现Namegetset访问器方法

图片.png

查看get_Nameset_NameIL代码,与自动属性的get_Nameset_Name方法的IL的代码的区别是多了一行IL_0000:  nop和代码大小。

那么显示实现gettersetter访问器有什么好处呢?

1.如果开始使用自动实现的属性public string Name { get; set; },访问该属性的任何代码实际都会调用getset方法。如果以后决定自己实现get方法和/set方法,而不是接收编译器的默认实现,访问属性的任何代码都不必重新编译。然后,如果将Name什么为字段,以后又想它更改为属性,那么访问字段的所有代码都必须重新编译,以便访问属性的方法。

2.自动实现的属性,不能再getset方法上添加一个断电,所以不好检测应用程序在什么时候获取或设置这个属性。相反,手动实现的属性可设置断点,查错时显得非常方便。但是调试时需要注意,如果对属性Name添加了监视,则可能会引入bug,比如在get访问器中递增一个字段count,那么每单步执行一行代码,监视器都会重新去调用get方法,从而造成字段的递增。

看下面的code,如果不进行debugcount=1,如果进行了debugcount值会随着监视Name属性而调用get方法的次数不同而不同

图片.png

003.gif

 

解决办法是在Visual Studio中关闭属性求值,工具->选项->调试->常规->不勾选启用属性求值和其他隐式函数调用。

图片.png

如果需要对监视器中的属性求值,可以手动强制属性求值:

图片.png

注意:

getset方法要么都显示实现,要么都自动实现

性能

1.简单的getset访问其方法,release版本中,JIT将代码内联(inline)。使用属性就没有性能上的损失。

2.JIT编译器在调试代码时不会内联属性方法,因为内联的代码回变得难以调试。

3.在程序的release版本中,访问属性时的性能可能比较快,在程序的调试版本中,则可能比较慢。

4.字段访问无论在调试还是release版本中很快

访问权限

通过上面的IL代码分析,我们已经对属性这个语法糖的本质更加清楚了。那么既然属性就是两个方法构成的,那么我们可以设置get_Nameget_Name方法的访问权限吗?

答案是肯定的。如下图所示,当我们想要对象sName属性时,提示get访问器不能访问

图片.png

那么我们是否能删掉其中一个getset方法呢?

答案是不能。如下图所示:提示必须定义getset访问器

图片.png

注意:

1.属性的访问级别可以是指定为任意访问级别

2.getset访问器的访问级别必须等于属性的访问级别或比属性的访问级别更大,如属性Name的访问级别是public,则getset访问器的访问级别必须是public,或是protected,private

3.getset访问器中只能有一个访问级别高于属性的访问级别,另外一个必须等于属性的访问级别

4.getset访问器的访问级别如果等于属性的访问级别,则必须省略访问性的关键字。如属性Name的访问级别是publicgetset的访问级别是public,则必须省略getset前面的public,否则VS编译不通过。

 

回到顶部

回到最开始提出的问题

1.属性和字段的区别?

属性其实是由两个访问器方法get_Name()set_Name()和一个隐藏字段<Name>k__BackingField构成。

2.为什么会有属性这个用法?

  1.属性中的get_Nameset_Name方法我们可以自己实现,从而可以在方法中加一些对数据的合理性检查,确保对象的状态永远不被破坏。其他的用法如:在WPF可以利用属性实现动态绑定。

  2.封装了字段的访问性。可以设置get方法是public的,set方法是private的,那么这个属性就是只读的。

 

回到顶部

参考资料

《你必须知道的.NET》

《CLR via C#》

【.Net底层剖析】stfld指令-给对象的字段赋值

IL指令速查

IL字节码分析

30分钟?不需要,轻松读懂IL




相关文章
|
API
.net core工具组件系列之Autofac—— 第二篇:Autofac的3种依赖注入方式(构造函数注入、属性注入和方法注入),以及在过滤器里面实现依赖注入
本篇文章接前一篇,建议可以先看前篇文章,再看本文,会有更好的效果。前一篇跳转链接:https://www.cnblogs.com/weskynet/p/15046999.html
414 0
.net core工具组件系列之Autofac—— 第二篇:Autofac的3种依赖注入方式(构造函数注入、属性注入和方法注入),以及在过滤器里面实现依赖注入
|
XML JSON 数据格式
.NET中XML序列化和反序列化常用类和用来控制XML序列化的属性总结(XmlSerializer,XmlTypeAttribute,XmlElementAttribute,XmlAttributeAttribute,XmlArrayAttribute...)
.NET中XML序列化和反序列化常用类和用来控制XML序列化的属性总结(XmlSerializer,XmlTypeAttribute,XmlElementAttribute,XmlAttributeAttribute,XmlArrayAttribute...)
244 0
【深入浅出.Net IL】1.一个For循环引发的IL
【深入浅出.Net IL】1.一个For循环引发的IL
110 0
【深入浅出.Net IL】1.一个For循环引发的IL
|
JSON 物联网 数据格式
阿里云物联网.NET Core客户端 CZGL.AliloTClient:5.设置设备属性
阿里云物联网.NET Core客户端 CZGL.AliloTClient:5.设置设备属性
327 0
|
存储 传感器 JSON
阿里云物联网.NET Core客户端|CZGL.AliloTClient:4.设备上报属性
阿里云物联网.NET Core客户端|CZGL.AliloTClient:4.设备上报属性
339 0
|
存储 JSON 物联网
.NET Core 跨平台物联关网开发:上报属性(三)
.NET Core 跨平台物联关网开发:上报属性(三)
186 0
.NET Core 跨平台物联关网开发:上报属性(三)
|
JSON 物联网 网络性能优化
NET Core 跨平台物联网开发 SDK属性、方法、委托、类(四)
NET Core 跨平台物联网开发 SDK属性、方法、委托、类(四)
246 0
|
JSON 前端开发 数据格式
.net MVC 使用 JSON JavaScriptSerializer 进行序列化或反序列化时出错,字符串的长度超过了为 maxJsonLength 属性设置的值
在.net mvc的controller中,方法返回JsonResult,一般我们这么写: [HttpPost] public JsonResult QueryFeature(string url, string whereClause) { string str=""; return Json(str); }   此时如果str过长,就会报“使用 JSON JavaScriptSerializer 进行序列化或反序列化时出错,字符串的长度超过了为 maxJsonLength 属性设置的值”。
2825 0
|
JSON 开发框架 前端开发
ASP.NET MVC中JSON强制小写属性名称
首先需要引用Newtonsoft.Json using Newtonsoft.Json; using Newtonsoft.Json.Serialization; 然后转换对象 Model.JsonResult jsonResult = new Model.
894 0
|
JSON 物联网 数据格式
阿里云物联网 .NET Core 客户端 | CZGL.AliIoTClient:5. 设置设备属性
在上一章,格力空调温度 gree_temperature 设置了 读写 权限,因为空调的输出温度是可以被设置的。 CPU 温度是根据实际情况进行采集,而 空调温度 是遥控器设置的,服务器可以或者这个温度数据,同时也可以设置这个数据。
1288 0