IL角度理解C#中字段,属性与方法的区别
IL角度理解C#中字段,属性与方法的区别
1.字段,属性与方法的区别
字段的本质是变量,直接在类或者结构体中声明。类或者结构体中会有实例字段,静态字段等(静态字段可实现内存共享功能,比如数学上的pi就可以存在静态字段)。一般来说字段应该带有private 或者 protected访问属性。一般来说字段需要通过类中的方法,或者属性来暴露给其他类。通过限制间接访问内部字段,来保护输入数据的安全。
属性的本质是类的一个成员,它提供了私有字段读,写,计算值的灵活机制。属性如果是数据成员能直接被使用,但本质是特殊方法,我们称之为访问器。它的作用是使得数据访问更容易,数据更安全,方法访问更灵活。属性使得类暴露了get,set公有方法,它隐藏了实现,get属性访问器,用来返回属性值,set属性访问器,用来设置值。综上,属性的本质是一对方法的包装,get,set。
他们是完全不同的语言元素。字段是类里保存数据的基本单元(变量),属性不能保存。
需要创建属性来控制私有变量(字段)基于对象的读写访问控制。
一个字段给其他类访问,只有两种方法,字段的访问属性修改为public,不建议,因为字段是可读可写的,无法阻止用户写某些字段,比如出生日期,只读不可写,使用属性。
字段不能抛出异常,调用方法,属性可以。
在属性里, Set 或者 Get 方法由编译器预定义好了。
2. 字段,属性与方法的IL代码
2.1 C#代码
主程序
class Program
{
static void Main(string[] args)
{
Person Tom = new Person();
Tom.SayHello();
Console.WriteLine("{0}", Tom.Name);
}
}
Person类
public class Person
{
private string _name;
public string _firstName;
public string Name
{
get
{
// return _name;
return "Hello";
}
set
{
_name = value;
}
}
public int Age{get;private set;} //AutoProperty generates private field for us
public void SayHello()
{
Console.WriteLine("Hello World!");
}
}
2.2 IL代码分析
2.2.1 字段的IL代码
可以看到字段的IL代码的关键字是 field。
.field private string _name
.field public string _firstName
2.2.2 属性的IL代码
2.2.2.1 属性
属性的IL关键字即是property。
.property instance string Name()
{
.get instance string FieldPropertyMethod.Person::get_Name()
.set instance void FieldPropertyMethod.Person::set_Name(string)
} // end of property Person::Name
点到对应的get,set访问器。
.method public hidebysig specialname instance string
get_Name() cil managed
{
.maxstack 1
.locals init (
[0] string V_0
)
IL_0000: nop
IL_0001: ldstr "Hello"
IL_0006: stloc.0 // V_0
IL_0007: br.s IL_0009
IL_0009: ldloc.0 // V_0
IL_000a: ret
} // end of method Person::get_Name
.method public hidebysig specialname instance void
set_Name(
string 'value'
) cil managed
{
.maxstack 8
IL_0000: nop
IL_0001: ldarg.0 // this
IL_0002: ldarg.1 // 'value'
IL_0003: stfld string FieldPropertyMethod.Person::_name
IL_0008: ret
} // end of method Person::set_Name
从上可以看出get,set访问器的本质就是方法(method).由上属性就是对get,set两种方法及其访问特性的封装。由此可见,属性就是对get,set方法的封装。
2.2.2.2 自动生成属性
a. 自动生成属性代码
代码量小,实用,此语法从C#3.0开始定义自动属性
public int Age{get;private set;}
b. 自动生成属性的IL代码分析
.property instance int32 Age()
{
.get instance int32 FieldPropertyMethod.Person::get_Age()
.set instance void FieldPropertyMethod.Person::set_Age(int32)
} // end of property Person::Age
} // end of class FieldPropertyMethod.Person
由上可以看出,其IL代码证明也是属性。继续看get,set字段属性方法。
.method public hidebysig specialname instance int32
get_Age() cil managed
{
.custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor()
= (01 00 00 00 )
.maxstack 8
IL_0000: ldarg.0 // this
IL_0001: ldfld int32 FieldPropertyMethod.Person::'<Age>k__BackingField'
IL_0006: ret
} // end of method Person::get_Age
.method private hidebysig specialname instance void
set_Age(
int32 'value'
) cil managed
{
.custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor()
= (01 00 00 00 )
.maxstack 8
IL_0000: ldarg.0 // this
IL_0001: ldarg.1 // 'value'
IL_0002: stfld int32 FieldPropertyMethod.Person::'<Age>k__BackingField'
IL_0007: ret
} // end of method Person::set_Age
k__BackingField 即是属性背后的字段变量,这是编译器自动生成的后台字段。由此自动属性与我们自己定义的属性功能一模一样。
2.2.3 方法的IL代码分析
IL代码中的关键字method即表示方法。
.method public hidebysig instance void
SayHello() cil managed
{
.maxstack 8
IL_0000: nop
IL_0001: ldstr "Hello World!"
IL_0006: call void [System.Console]System.Console::WriteLine(string)
IL_000b: nop
IL_000c: ret
} // end of method Person::SayHello
备注:本IL代码由rider的IL View功能产生
3 属性的功能
3.1 设置只读属性
像出生年月这种只读不能写的属性,易用属性。
public datetime birthday{get;private set;}
3.2 调用方法
在属性Count中调用CalculateNoOfRows方法;
public class Rows
{
private string _count;
public int Count
{
get
{
return CalculateNoOfRows();
}
}
public int CalculateNoOfRows()
{
// Calculation here and finally set the value to _count
return _count;
}
}
3.3 赖加载
有些数据加载的功能可以放在属性中加载,不放在构造函数中,以此来加快对象创建的速度。
3.4 接口继承
可以对接口里的属性进行继承,而字段不行;
3.5 属性做个简单的校验
class Name
{
private string MFullName="";
private int MYearOfBirth;
public string FullName
{
get
{
return(MFullName);
}
set
{
if (value==null)
{
throw(new InvalidOperationException("Error !"));
}
MFullName=value;
}
}
public int YearOfBirth
{
get
{
return(MYearOfBirth);
}
set
{
if (MYearOfBirth<1900 || MYearOfBirth>DateTime.Now.Year)
{
throw(new InvalidOperationException("Error !"));
}
MYearOfBirth=value;
}
}
public int Age
{
get
{
return(DateTime.Now.Year-MYearOfBirth);
}
}
public string FullNameInUppercase
{
get
{
return(MFullName.ToUpper());
}
}
}
例子而已,ddd中一般来说值对象来定义,校验也同样会放在值对象中。
3.6 属性中调用事件
public class Person {
private string _name;
public event EventHandler NameChanging;
public event EventHandler NameChanged;
public string Name{
get
{
return _name;
}
set
{
OnNameChanging();
_name = value;
OnNameChanged();
}
}
private void OnNameChanging(){
NameChanging?.Invoke(this,EventArgs.Empty);
}
private void OnNameChanged(){
NameChanged?.Invoke(this,EventArgs.Empty);
}
4 字段的优越性
字段作为属性的存储基元功用之外,还有没有应用场景是性能超越属性的呢?答案是肯定的,字段作为ref/out参数时,性能更优异,
下面举一例。
4.1 属性赋值代码
class Program
{
static void Main(string[] args)
{
#region 属性性能测试
Point[] points = new Point[1000000];
Initializ(points);
var bigRunTime = DateTime.Now;
for (int i = 0; i < points.Length; i++)
{
int x = points[i].X;
int y = points[i].Y;
TransformPoint(ref x, ref y);
points[i].X = x;
points[i].Y = y;
}
var endRunTime = DateTime.Now;
var timeSpend=ExecDateDiff(bigRunTime,endRunTime);
Console.WriteLine("变换后首元素坐标:{0},{1}",points[0].X,points[0].Y);
Console.WriteLine("程序执行花费时间:{0}",timeSpend);
#endregion
}
/// 程序执行时间测试
/// </summary>
/// <param name="dateBegin">开始时间</param>
/// <param name="dateEnd">结束时间</param>
/// <returns>返回(秒)单位,比如: 0.00239秒</returns>
public static string ExecDateDiff(DateTime dateBegin, DateTime dateEnd)
{
TimeSpan ts1 = new TimeSpan(dateBegin.Ticks);
TimeSpan ts2 = new TimeSpan(dateEnd.Ticks);
TimeSpan ts3 = ts1.Subtract(ts2).Duration();
//你想转的格式
return ts3.TotalMilliseconds.ToString();
}
static Point[] Initializ(Point[] points)
{
for (int i = 0; i < points.Length; i++)
{
points[i] =new Point();
points[i].X = 1;
points[i].Y = 2;
}
Console.WriteLine("首元素坐标:{0},{1}",points[0].X,points[0].Y);
return points;
}
static void TransformPoint(ref int x, ref int y)
{
x = 3;
y = 4;
}
}
public class Point
{
public int X { get; set; }
public int Y { get; set; }
}
这里属性为什么不能直接绑定ref参数呢?rider的智能提示给我们做了解答
翻译过来的意思是属性返回的是临时变量,ref需要绑定特定的变量,如字段,数组元素等。
属性拷贝需要的时间:
花费时间大约是31ms。
4.2 字段赋值
class Program
{
static void Main(string[] args)
{
#region 字段性能测试
PointField[] points = new PointField[1000000];
InitializField(points);
var bigRunTime = DateTime.Now;
for (int i = 0; i < points.Length; i++)
{
TransformPoint(ref points[i].X, ref points[i].Y);
}
var endRunTime = DateTime.Now;
var timeSpend=ExecDateDiff(bigRunTime,endRunTime);
Console.WriteLine("变换后首元素坐标:{0},{1}",points[0].X,points[0].Y);
Console.WriteLine("字段赋值执行花费时间:{0}",timeSpend);
#endregion
}
/// 程序执行时间测试
/// </summary>
/// <param name="dateBegin">开始时间</param>
/// <param name="dateEnd">结束时间</param>
/// <returns>返回(秒)单位,比如: 0.00239秒</returns>
public static string ExecDateDiff(DateTime dateBegin, DateTime dateEnd)
{
TimeSpan ts1 = new TimeSpan(dateBegin.Ticks);
TimeSpan ts2 = new TimeSpan(dateEnd.Ticks);
TimeSpan ts3 = ts1.Subtract(ts2).Duration();
//你想转的格式
return ts3.TotalMilliseconds.ToString();
}
static PointField[] InitializField(PointField[] points)
{
for (int i = 0; i < points.Length; i++)
{
points[i] =new PointField();
points[i].X = 1;
points[i].Y = 2;
}
Console.WriteLine("首元素坐标:{0},{1}",points[0].X,points[0].Y);
return points;
}
static void TransformPoint(ref int x, ref int y)
{
x = 3;
y = 4;
}
}
public class PointField
{
public int X;
public int Y;
}
综上,使用字段的性能比使用属性性能提升了38.7%(31-19/31=38.7%),很可观。
究其原因,属性开辟了临时变量作为中转进行了深拷贝,而字段则是直接对地址(指针)进行解引用,直接赋值。
出赋值速度提升外,字段不需开辟临时内存,更加节省内存。
5 小技巧
在vs中prop 按tab键可自动生成属性
6 ref引用的本质
写在文末,也算是本文的彩蛋。该方法的形参通过关键字ref将变量设置成了引用。
static void TransformPoint(ref int x, ref int y)
{
x = 3;
y = 4;
}
引用ref的IL代码
.method private hidebysig static void
TransformPoint(
int32& x,
int32& y
) cil managed
对没错,你看到了&,熟悉C语言的道友知道,在这里是取了传入整形变量的地址。所以在方法里进行解引用赋值,就能改变形参的值,
本质就是通过指针(传入变量的地址)来对形参值的修改。
参考文章:
What is the difference between a field and a property?
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/JerryMouseLi/p/13855733.html
IL角度理解C#中字段,属性与方法的区别的更多相关文章
- 深入理解jQuery中live与bind方法的区别
本篇文章主要是对jQuery中live与bind方法的区别进行了详细的分析介绍,需要的朋友可以过来参考下,希望对大家有所帮助 注意如果是通过jq添加的层和对象一定要用live(),用其他的都不起作用 ...
- IL角度理解for 与foreach的区别——迭代器模式
IL角度理解for 与foreach的区别--迭代器模式 目录 IL角度理解for 与foreach的区别--迭代器模式 1 最常用的设计模式 1.1 背景 1.2 摘要 2 遍历元素 3 删除元素 ...
- 深入理解css中position属性及z-index属性
深入理解css中position属性及z-index属性 在网页设计中,position属性的使用是非常重要的.有时如果不能认识清楚这个属性,将会给我们带来很多意想不到的困难. position属性共 ...
- 深入理解JavaScript中的属性和特性
深入理解JavaScript中的属性和特性 JavaScript中属性和特性是完全不同的两个概念,这里我将根据自己所学,来深入理解JavaScript中的属性和特性. 主要内容如下: 理解JavaSc ...
- 深入理解css中position属性及z-index属性 https://www.cnblogs.com/zhuzhenwei918/p/6112034.html
深入理解css中position属性及z-index属性 请看出处:https://www.cnblogs.com/zhuzhenwei918/p/6112034.html 在网页设计中,positi ...
- 理解JAVA - 面向对象(object) - 属性,方法
理解JAVA - 面向对象(object) - 属性,方法 多态的体现: 向上造型,父类接收子类对象:向上造型: 从父类角度看不到子类独有的方法:面向对象,人类认知世界的方式:生活中每天都 ...
- 简单理解Struts2中拦截器与过滤器的区别及执行顺序
简单理解Struts2中拦截器与过滤器的区别及执行顺序 当接收到一个httprequest , a) 当外部的httpservletrequest到来时 b) 初始到了servlet容器 传递给一个标 ...
- mybatis生成的pojo 中的属性或方法不够我们当做dto使用时
我们在写代码的时候,如果一个 mybatis生成的pojo 中的属性或方法不够我们使用(当做dto和前台交互)时,我们有两种方法: 第一: 直接在 原 pojo 中增加属性或者方法 第二:我们可以再写 ...
- JS高级---拷贝继承:把一个对象中的属性或者方法直接复制到另一个对象中
拷贝继承:把一个对象中的属性或者方法直接复制到另一个对象中 浅拷贝 function Person() { } Person.prototype.age = 10; Person.prototype. ...
随机推荐
- 微信小程序入门到精通[更新版]
微信小程序账号与工具 在线文档:https://mp.weixin.qq.com/debug/wxadoc/dev/ 小程序开发者账号注册 微信公众平台:https://mp.weixin.qq.co ...
- Oracle学习(十五)PLSQL安装
PS:由于原来一直用的旧版本的PLSQL客户端,查看执行计划有些数据无法展示,所以今天换一波新版本的使用,记录下安装和使用流程. PLSQL(oracle数据可视化工具) 一.下载 我用的13的版本, ...
- vue学习09 图片切换
目录 vue学习09 图片切换 定义图片数组:imgList:[],列表数据使用数组保存 添加图片索引:index 绑定src属性:使用v-bind,v-bind指令可以设置元素属性,比如src 图片 ...
- Redis深入浅出
一.基础使用 常用命令 keys,expire(过期),ttl(查看生存时间),set,select,dbsize,flushdb(删除当前库),flushall(删除所有), get,append, ...
- 什么是 Opcache,如何使用 Opcache
Opcode 是啥? 我们先看一下 PHP 的执行过程: PHP 初始化执行环节,启动 Zend 引擎,加载注册的扩展模块. 初始化后读取 PHP 脚本文件,Zend 引擎对 PHP 文件进行词法分析 ...
- webservice了解一下!!
(1)什么是webservice? webservice是一种可以跨编程语言和跨平台进行远程调用的一种技术,是同步进行. webservice主要分为两种,一种是基于浏览器的瘦客户端应用程序,一种是基 ...
- 再解决不了前端加密我就吃shi
参考文章 快速定位前端加密方法 渗透测试-前端加密测试 前言 最近学习挖洞以来,碰到数据做了加密基本上也就放弃了.但是发现越来越多的网站都开始做前端加密了,不论是金融行业还是其他.所以趁此机会来捣鼓一 ...
- 020 01 Android 零基础入门 01 Java基础语法 02 Java常量与变量 14 变量与常量 知识总结
020 01 Android 零基础入门 01 Java基础语法 02 Java常量与变量 14 变量与常量 知识总结 本文知识点:变量与常量 知识总结 Java中的标识符 Java中的关键字 目前常 ...
- 玩转 SpringBoot2.x 之整合 thumbnailator 图片处理
1.序 在实际项目中,有时为了响应速度,难免会对一些高清图片进行一些处理,比如图片压缩之类的,而其中压缩可能就是最为常见的.最近,阿淼就被要求实现这个功能,原因是客户那边嫌速度过慢.借此机会,阿淼今儿 ...
- 实现Excel文件的上传和解析
前言 本文思维导图 一.需求描述 实现一个页面上传excel的功能,并对excel中的内容做解析,最后存储在数据库中. 二.代码实现 需求实现思路: 先对上传的文件做校验和解析,这里我们通过Excel ...