IL来理解属性
IL来理解属性
2.【.Net底层剖析】2.stfld指令-给对象的字段赋值
未完待续......
概述:
我们经常在code中用到属性,但是我们真的知道属性和字段的区别吗?为什么会有属性这个用法?带着这两个问题,我们来用IL中间语言剖析一下属性(Property)
C#中如何定义一个属性
|
1
|
public string name { get; set; } |

编译之后,用ildasm.exe工具看下IL代码
ildasm.exe 路径:C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools 不同的机器,ildasm.exe路径可能不同

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

Student类
1.查看更多信息

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

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

双击进去查看IL代码

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

私有字段<Name>K__BackingField,string类型,
2.get_Name方法

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

所以get_Name的作用就是得到隐藏字段<Name>k__BackingField的值
s.Name相当于得到了隐藏字段<Name>k__BackingField的值
3.set_Name方法


所以set_Name的作用就是将set_Name中的参数value赋值给隐藏字段<Name>k__BackingField
s.Name = "Jackson" 相当于value="Jackson",将value赋值给<Name>k__BackingField
4.属性Name


Main方法


newobj指令:
- 从托管堆分配指定类型所需要的全部内存空间。
- 从调用执行构造函数初始化之前,首先初始化对象的附加成员:
- 指向该类型方法表的指针
- SyncBlockIndex,用于进行线程同步。
所有的对象都包含这两个附加成员,用于管理对象。
- 调用构造函数ctor,进行初始化操作,并返回新建对象的引用地址,将引用地址加入Execution Stack
所以s.Name就是调用set_Name(string value)方法,将"Jackson"的值传给set_Name,setName方法中将value的值赋值给隐藏字段<Name>k__BackingField
实现get,set方法
上面的Name属性是一个自动实现的属性
下面我们来显示实现Name的get和set访问器方法

查看get_Name和set_Name的IL代码,与自动属性的get_Name和set_Name方法的IL的代码的区别是多了一行IL_0000: nop和代码大小。
那么显示实现getter和setter访问器有什么好处呢?
1.如果开始使用自动实现的属性public string Name { get; set; },访问该属性的任何代码实际都会调用get和set方法。如果以后决定自己实现get方法和/或set方法,而不是接收编译器的默认实现,访问属性的任何代码都不必重新编译。然后,如果将Name什么为字段,以后又想它更改为属性,那么访问字段的所有代码都必须重新编译,以便访问属性的方法。
2.自动实现的属性,不能再get和set方法上添加一个断电,所以不好检测应用程序在什么时候获取或设置这个属性。相反,手动实现的属性可设置断点,查错时显得非常方便。但是调试时需要注意,如果对属性Name添加了监视,则可能会引入bug,比如在get访问器中递增一个字段count,那么每单步执行一行代码,监视器都会重新去调用get方法,从而造成字段的递增。
看下面的code,如果不进行debug,count=1,如果进行了debug,count值会随着监视Name属性而调用get方法的次数不同而不同。


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

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

注意:
get和set方法要么都显示实现,要么都自动实现
性能
1.简单的get和set访问其方法,release版本中,JIT将代码内联(inline)。使用属性就没有性能上的损失。
2.JIT编译器在调试代码时不会内联属性方法,因为内联的代码回变得难以调试。
3.在程序的release版本中,访问属性时的性能可能比较快,在程序的调试版本中,则可能比较慢。
4.字段访问无论在调试还是release版本中很快
访问权限
通过上面的IL代码分析,我们已经对属性这个语法糖的本质更加清楚了。那么既然属性就是两个方法构成的,那么我们可以设置get_Name和get_Name方法的访问权限吗?
答案是肯定的。如下图所示,当我们想要对象s的Name属性时,提示get访问器不能访问

那么我们是否能删掉其中一个get或set方法呢?
答案是不能。如下图所示:提示必须定义get和set访问器

注意:
1.属性的访问级别可以是指定为任意访问级别
2.get和set访问器的访问级别必须等于属性的访问级别或比属性的访问级别更大,如属性Name的访问级别是public,则get和set访问器的访问级别必须是public,或是protected,private等
3.get和set访问器中只能有一个访问级别高于属性的访问级别,另外一个必须等于属性的访问级别
4.get和set访问器的访问级别如果等于属性的访问级别,则必须省略访问性的关键字。如属性Name的访问级别是public,get和set的访问级别是public,则必须省略get和set前面的public,否则VS编译不通过。
回到最开始提出的问题
1.属性和字段的区别?
属性其实是由两个访问器方法get_Name()和set_Name()和一个隐藏字段<Name>k__BackingField构成。
2.为什么会有属性这个用法?
1.属性中的get_Name和set_Name方法我们可以自己实现,从而可以在方法中加一些对数据的合理性检查,确保对象的状态永远不被破坏。其他的用法如:在WPF可以利用属性实现动态绑定。
2.封装了字段的访问性。可以设置get方法是public的,set方法是private的,那么这个属性就是只读的。
参考资料
《你必须知道的.NET》
《CLR via C#》
作 者: Jackson0714
IL来理解属性的更多相关文章
- 【.Net底层剖析】3.用IL来理解属性
.Net底层剖析目录章节 1.[深入浅出.Net IL]1.一个For循环引发的IL 2.[.Net底层剖析]2.stfld指令-给对象的字段赋值 3.[.Net底层剖析]3.用IL来理解属性 未完待 ...
- IL角度理解C#中字段,属性与方法的区别
IL角度理解C#中字段,属性与方法的区别 1.字段,属性与方法的区别 字段的本质是变量,直接在类或者结构体中声明.类或者结构体中会有实例字段,静态字段等(静态字段可实现内存共享功能,比如数学上的pi就 ...
- 玩转动态编译 - 高级篇:一,IL访问静态属性和字段
IL介绍 通用中间语言(Common Intermediate Language,简称CIL,发音为"sill"或"kill")是一种属于通用语言架构和.NET ...
- 一,IL访问静态属性和字段
一,IL访问静态属性和字段 IL介绍 通用中间语言(Common Intermediate Language,简称CIL,发音为"sill"或"kill")是一 ...
- IL角度理解for 与foreach的区别——迭代器模式
IL角度理解for 与foreach的区别--迭代器模式 目录 IL角度理解for 与foreach的区别--迭代器模式 1 最常用的设计模式 1.1 背景 1.2 摘要 2 遍历元素 3 删除元素 ...
- Effective Objective-C 2.0 — 第二章 对象、消息、运行期 - 第六条:理解“属性”这一概念
开发者通过对象来 存储并传递数据. 在对象之间传递数据并执行任务的过程就叫做“消息传递”. 这两条特性的工作原理? Objective-C运行期环境(Objective-C runtime) ,提供了 ...
- - 高级篇:二,IL设置静态属性,字段和类型转换
- 高级篇:二,IL设置静态属性,字段和类型转换 静态属性赋值 先来看 Reflector反射出的IL源码(感谢Moen的提示),这次用 Release模式编译,去掉那些无用的辅助指令 public ...
- 玩转动态编译 - 高级篇:二,IL设置静态属性,字段和类型转换
静态属性赋值 先来看 Reflector反射出的IL源码(感谢Moen的提示),这次用 Release模式编译,去掉那些无用的辅助指令 public void AAA(string s) { MyCl ...
- 【深入浅出.Net IL】1.一个For循环引发的IL
.Net底层剖析目录章节 1.[深入浅出.Net IL]1.一个For循环引发的IL 2.[.Net底层剖析]2.stfld指令-给对象的字段赋值 3.[.Net底层剖析]3.用IL来理解属性 1.准 ...
随机推荐
- 菜鸟nginx源代码剖析数据结构篇(一)动态数组ngx_array_t
菜鸟nginx源代码剖析数据结构篇(一)动态数组ngx_array_t Author:Echo Chen(陈斌) Email:chenb19870707@gmail.com Blog:Blog.csd ...
- uva 1346 - Songs(贪心)
题目链接:uva 1346 - Songs 题目大意:John Doe 是一个著名的DJ,现在他有n首播放个曲, 每首歌曲有识别符key,歌曲长度l,以及播放频率q.想在John Doe 想将磁带上的 ...
- Python 链接MysqlDB
下载安装MySQLdb <1>linux版本 http://sourceforge.net/projects/mysql-python/ 下载,在安装是要先安装setuptools,然后在 ...
- Monkey 命令使用说明
1. 命令使用 Monkey是一个命令列工具 ,可以运行在仿真器里或实际设备中.它向系统发送伪随机的使用者事件流,实现对正在开发的应用程序进行压力测试.Monkey包括许多选项,它们大致分为四大类: ...
- Struts2图片文件上传,判断图片格式和图片大小
1. 配置Struts2能够上传的最大文件大小 使用Struts2进行文件上传的时候,Struts2默认文件大小最大为2MB,如果要传大一点的文件,就需要修改struts.xml配置文件,重新设置能够 ...
- cocos2d-x关于CCTableView的“乱序问题”的理解
Cocos2d-x有一个不错的控件CCTableView.之前用的时候发现有cell的顺序错乱和重复出现的问题.后来仔细看了一下源码,发现是自己用法错误.但是网上有人说是一个bug,要改源码.我发现2 ...
- asp.net web api帮助文档的说明
为asp.net的mvc web api填写自己的帮助文档 1. 加入Help的area(能够通过命令行或其它方式加入) 命令行:Install-Package Microsoft.AspNet.We ...
- Swift - 可选类型说明
可选类型是Swift的一个特色.它表示一个变量有可能有值,也可能没有值(nil). 声明的方式是“数据类型+问号”. 而当要使用一个可选类型的变量是,要在后面加感叹号“!”. 1 2 3 4 5 6 ...
- libevent简单分析
一看名字就知道是围绕eventloop转的. 那首先肯定是eventloop是个什么?一般都是IO事件,timer事件的管理器. 那首先看如何new出来一个eventloop: 1.因为libeven ...
- Swift - 复杂数据类型说明(数组,字典,结构体,枚举)
1,数组 - Array 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 var types ...