C# 程序性能提升篇-1、装箱和拆箱,枚举的ToString浅析
前景提要:
编写程序时,也许你不经意间,就不知不觉的使程序代码,发生了装箱和拆箱,从而降低了效率,不要说就发生那么一次两次,如果说是程序中发生了循环、网络程序(不断请求处理的)等这些时候,减少装箱和拆箱,是优化程序提高效率的一种途径。不仅跬步,无以至千里,不积小流,无以至江河。优化从点点滴滴做起。
一、装箱拆箱概念:
这里是官方定义:http://msdn.microsoft.com/zh-cn/library/yz2be5wk.aspx
装箱:值类型→引用类型
拆箱:引用类型→值类型
二、为什么说装箱,拆箱消耗资源(内存、cpu)?
2.1 图说装箱、拆箱

说明:装箱。值类型存放于内存栈上,引用类型存放于内存对上。如果将已定义好的值类型(栈上的数据)修改至引用类型(堆上),
2.2 图文说 装箱过程
值类型存储(没有堆什么事): 引用类型存储(栈中存储的是,堆中对象的地址,堆中是实际对象)

这时如果,将值类型变成引用类型,存储的位置发生变化,发生了装箱,而且为了拆箱,现在引用类型的存储模式也不仅仅是以上引用类型的存储模型了,值类型的类型也会进行相应的存储,以方便在拆箱时候,转换成相应装箱时的类型。
这样可以看出,装箱,其实比你直接定义成一个引用类型,给家消耗了内存,以及增加了计算量(消耗了cpu)。这是原理级别的解释,跟深入的,我也不太清除。只能分析到CLR这一步。
三、浅谈ToString()
估计大家都知道,C#所有的类型基类(父类)均是Object,而Object中,提供的能叫子类继承的方法就那么几个,virtual 的ToString就是其中之一,所以说,c#中所有的类型均有这个ToString方法。下面就浅谈一下ToString方法在装箱拆箱中的一二。
3.1 针对普通值类型
以Int32为例(Struct)
int a=123; string b=a.ToString();
请问这是发生装箱了吗?
答:值类型→引用类型,oh,装箱!!
解答:只单纯的看装箱定义,这确实符合装箱的定义。但是,别忘记了ToString是基类的虚方法,子类是否对其有重写。
int 的 户口祖籍
int(C#语言)→Int32(CLR,oh是个结构,struct)
→extends System.ValueType(查看IL代码,发现了)→extends System.Object(这是终极祖宗啊!这里有ToString啊)
这是Int32中对ToString方法的重写:
public override string ToString() { return Number.FormatInt32(this, null, NumberFormatInfo.CurrentInfo);}
接下来就是内部的实现了,我去,看不到了啊?怎么办?
对了编写代码,查看IL代码。

可以看出这里没有发生装箱啊!具体的深入内部实现可以借助反编译工具,查看,如ILSpy、reflecter、ILdasm等。
3.2枚举类型
那么所有的值类型是不是使用Tostring方法,均不涉及装箱操作呢?这个也不尽然,可是尝试一下枚举类型。
枚举类型,是一个值类型。
示例:
enum TestEnum { Test1, Test2 }
string test = TestEnum.Test1.ToString(); //这句话是否发生装箱操作
3.2.1 内部原理
首先查看枚举中的ToString方法,这里重写了ToString方法
public override string ToString() { return InternalFormat((RuntimeType) base.GetType(), this.GetValue());}
查看InternalFormat方法的实现
private static string InternalFormat(RuntimeType eT, object value) { if (eT.IsDefined(typeof(FlagsAttribute), false)) { return InternalFlagsFormat(eT, value); } string name = GetName(eT, value); if (name == null) { return value.ToString(); } return name; }
通过查看可知eT.IsDefined(typeof(FlagsAttribute), false)、GetName(),这里使用了反射,可能会有性能的损失,但是不会有装箱操作
但GetName(eT,value),中的value参数是InternalFormat中的参数,这里的参数是object类型,
而InternalFormat((RuntimeType) base.GetType(), this.GetValue())调用时,这里的使用了this.GetValue这个方法来传递这个object参数
接下来查看 GetValue方法的实现啦

可以看出关于这个GetValue方法中发生了,装箱操作,return (bool) *(((sbyte*) ptrRef)); 这个一个值类型,而GetValue需要的返回值是:Object类型
结论,枚举中重写的ToString方法不仅使用到了装箱操作,而且还是用到了大量的反射。
综上所述,使用枚举时,只是针对值类型操作,增加几个常量状态switch-case,以及不涉及取出枚举定义的值(ToString)则是非常方便的,快速的。
但是要是经常使用枚举的ToString取得枚举的定义值,则不建议使用。这里是非常不合时宜的。可以直接使用静态类代替即可(使用空间换取时间)
3.3 分析网络大牛的技术博客
原本装箱、拆箱感觉写的差不多了,但是看到网上那么多大牛、那么写感觉有点不合适啊!(不要被他们所谓的比较性能吓到哦)
3.3.1 博客地址:http://www.cnblogs.com/XmNotes/archive/2010/09/18/1830355.html
这是第一个:性能相差7千倍的ToString方法 的博客
解说:看到标题,第一句想说的是,我靠!这么雷人啊。7千倍啊!
但是一看代码你就知道他在干嘛了
var day = DayOfWeek.Wednesday; //这可是枚举啊
for (int i = 0; i < 1000000; i++) { value = day.ToString(); }
百万级别的反射、装箱。你坑人呢吧,不说实际有没有这么百万级别的数量和这么频繁的操作,就说有你这么用的吗!
一种是你直接返回一个值类型的星期,最后表现层给你转换一下,即使这里装箱、拆箱也就是这么一次两个,还能百万级别的刷啊!
还有就是类似你的第二种,直接就是操作引用类型的,如果像你这样百万级别的在转换一下,弄成静态常量。
结论是:举例要以事实做依据,不要做不符合实际的事情。不同的方法、类库用于适合的场景。这里不仅仅反射会耗时,装箱操作也会造成资源的消耗C# 程序性能提升篇-1、装箱和拆箱,枚举的ToString浅析
前景提要:
编写程序时,也许你不经意间,就不知不觉的使程序代码,发生了装箱和拆箱,从而降低了效率,不要说就发生那么一次两次,如果说是程序中发生了循环、网络程序(不断请求处理的)等这些时候,减少装箱和拆箱,是优化程序提高效率的一种途径。不仅跬步,无以至千里,不积小流,无以至江河。优化从点点滴滴做起。
一、装箱拆箱概念:
这里是官方定义:http://msdn.microsoft.com/zh-cn/library/yz2be5wk.aspx
装箱:值类型→引用类型
拆箱:引用类型→值类型
二、为什么说装箱,拆箱消耗资源(内存、cpu)?
2.1 图说装箱、拆箱

说明:装箱。值类型存放于内存栈上,引用类型存放于内存对上。如果将已定义好的值类型(栈上的数据)修改至引用类型(堆上),
2.2 图文说 装箱过程
值类型存储(没有堆什么事): 引用类型存储(栈中存储的是,堆中对象的地址,堆中是实际对象)

这时如果,将值类型变成引用类型,存储的位置发生变化,发生了装箱,而且为了拆箱,现在引用类型的存储模式也不仅仅是以上引用类型的存储模型了,值类型的类型也会进行相应的存储,以方便在拆箱时候,转换成相应装箱时的类型。
这样可以看出,装箱,其实比你直接定义成一个引用类型,给家消耗了内存,以及增加了计算量(消耗了cpu)。这是原理级别的解释,跟深入的,我也不太清除。只能分析到CLR这一步。
三、浅谈ToString()
估计大家都知道,C#所有的类型基类(父类)均是Object,而Object中,提供的能叫子类继承的方法就那么几个,virtual 的ToString就是其中之一,所以说,c#中所有的类型均有这个ToString方法。下面就浅谈一下ToString方法在装箱拆箱中的一二。
3.1 针对普通值类型
以Int32为例(Struct)
int a=123; string b=a.ToString();
请问这是发生装箱了吗?
答:值类型→引用类型,oh,装箱!!
解答:只单纯的看装箱定义,这确实符合装箱的定义。但是,别忘记了ToString是基类的虚方法,子类是否对其有重写。
int 的 户口祖籍
int(C#语言)→Int32(CLR,oh是个结构,struct)
→extends System.ValueType(查看IL代码,发现了)→extends System.Object(这是终极祖宗啊!这里有ToString啊)
这是Int32中对ToString方法的重写:
public override string ToString() { return Number.FormatInt32(this, null, NumberFormatInfo.CurrentInfo);}
接下来就是内部的实现了,我去,看不到了啊?怎么办?
对了编写代码,查看IL代码。

可以看出这里没有发生装箱啊!具体的深入内部实现可以借助反编译工具,查看,如ILSpy、reflecter、ILdasm等。
3.2枚举类型
那么所有的值类型是不是使用Tostring方法,均不涉及装箱操作呢?这个也不尽然,可是尝试一下枚举类型。
枚举类型,是一个值类型。
示例:
enum TestEnum { Test1, Test2 }
string test = TestEnum.Test1.ToString(); //这句话是否发生装箱操作
3.2.1 内部原理
首先查看枚举中的ToString方法,这里重写了ToString方法
public override string ToString() { return InternalFormat((RuntimeType) base.GetType(), this.GetValue());}
查看InternalFormat方法的实现
private static string InternalFormat(RuntimeType eT, object value) { if (eT.IsDefined(typeof(FlagsAttribute), false)) { return InternalFlagsFormat(eT, value); } string name = GetName(eT, value); if (name == null) { return value.ToString(); } return name; }
通过查看可知eT.IsDefined(typeof(FlagsAttribute), false)、GetName(),这里使用了反射,可能会有性能的损失,但是不会有装箱操作
但GetName(eT,value),中的value参数是InternalFormat中的参数,这里的参数是object类型,
而InternalFormat((RuntimeType) base.GetType(), this.GetValue())调用时,这里的使用了this.GetValue这个方法来传递这个object参数
接下来查看 GetValue方法的实现啦

可以看出关于这个GetValue方法中发生了,装箱操作,return (bool) *(((sbyte*) ptrRef)); 这个一个值类型,而GetValue需要的返回值是:Object类型
结论,枚举中重写的ToString方法不仅使用到了装箱操作,而且还是用到了大量的反射。
综上所述,使用枚举时,只是针对值类型操作,增加几个常量状态switch-case,以及不涉及取出枚举定义的值(ToString)则是非常方便的,快速的。
但是要是经常使用枚举的ToString取得枚举的定义值,则不建议使用。这里是非常不合时宜的。可以直接使用静态类代替即可(使用空间换取时间)
3.3 分析网络大牛的技术博客
原本装箱、拆箱感觉写的差不多了,但是看到网上那么多大牛、那么写感觉有点不合适啊!(不要被他们所谓的比较性能吓到哦)
3.3.1 博客地址:http://www.cnblogs.com/XmNotes/archive/2010/09/18/1830355.html
这是第一个:性能相差7千倍的ToString方法 的博客
解说:看到标题,第一句想说的是,我靠!这么雷人啊。7千倍啊!
但是一看代码你就知道他在干嘛了
var day = DayOfWeek.Wednesday; //这可是枚举啊
for (int i = 0; i < 1000000; i++) { value = day.ToString(); }
百万级别的反射、装箱。你坑人呢吧,不说实际有没有这么百万级别的数量和这么频繁的操作,就说有你这么用的吗!
一种是你直接返回一个值类型的星期,最后表现层给你转换一下,即使这里装箱、拆箱也就是这么一次两个,还能百万级别的刷啊!
还有就是类似你的第二种,直接就是操作引用类型的,如果像你这样百万级别的在转换一下,弄成静态常量。
结论是:举例要以事实做依据,不要做不符合实际的事情。不同的方法、类库用于适合的场景。这里不仅仅反射会耗时,装箱操作也会造成资源的消耗
3.3.2 博客地址:http://www.cnblogs.com/yjmyzz/archive/2010/09/19/1830766.html
这是第二个:也谈枚举ToString()性能的改进 的博客
解说:我不理解楼主在干吗,你定义的静态类,在第一次使用的时候,就已经将枚举装到静态变量dictionary中了,常驻内存了,直到程序结束才推出。
类似于你没事循环读取百万级别的一个静态变量啊!
而使用枚举的ToString方法,是你在百万级别的反射、装箱数据啊!
我晕啊!枚举是这样用,这样理解的吗?
如果这几个定义你常用、百万级别读取的话,你能不能稍微浪费点内存啊!直接这样用啊(空间换取时间)
public static class EnumLoginError
{
public static string 用户名不存在 { get{return "用户名不存在";}}
public static string 密码错误 { get{return "密码错误";}}
public static string 用户被锁定 { get{return "用户被锁定";}}
public static string 未知错误 { get{return "未知错误";}}
} 结论是:不要做画蛇添足的事情,对待事物要有怀疑精神。还是合适的工具做合适的活,合适的人做合适事情。 四、能够减少拆箱装箱,常用的替代类库 4.1 推荐使用泛型集合 命名空间System.Collections.Generic List<T>类似于ArrayList,ArrayList的升级版。 各种方法:Sort()、Max()、Min()、Sum()… Dictionary<K,V>类似于Hashtable,Hashtable的升级版。 T,K,V就像一把锁,锁住集合只能存某种特定的类型,这里的T,K,V也可以是其它字母
3.3.2 博客地址:http://www.cnblogs.com/yjmyzz/archive/2010/09/19/1830766.html
这是第二个:也谈枚举ToString()性能的改进 的博客
解说:我不理解楼主在干吗,你定义的静态类,在第一次使用的时候,就已经将枚举装到静态变量dictionary中了,常驻内存了,直到程序结束才推出。
类似于你没事循环读取百万级别的一个静态变量啊!
而使用枚举的ToString方法,是你在百万级别的反射、装箱数据啊!
我晕啊!枚举是这样用,这样理解的吗?
如果这几个定义你常用、百万级别读取的话,你能不能稍微浪费点内存啊!直接这样用啊(空间换取时间)
public static class EnumLoginError
{
public static string 用户名不存在 { get{return "用户名不存在";}}
public static string 密码错误 { get{return "密码错误";}}
public static string 用户被锁定 { get{return "用户被锁定";}}
public static string 未知错误 { get{return "未知错误";}}
} 结论是:不要做画蛇添足的事情,对待事物要有怀疑精神。还是合适的工具做合适的活,合适的人做合适事情。 四、能够减少拆箱装箱,常用的替代类库 4.1 推荐使用泛型集合 命名空间System.Collections.Generic List<T>类似于ArrayList,ArrayList的升级版。 各种方法:Sort()、Max()、Min()、Sum()… Dictionary<K,V>类似于Hashtable,Hashtable的升级版。 T,K,V就像一把锁,锁住集合只能存某种特定的类型,这里的T,K,V也可以是其它字母
C# 程序性能提升篇-1、装箱和拆箱,枚举的ToString浅析的更多相关文章
- C# 程序性能提升篇-2、类型(字段类型、class和struct)的错误定义所影响性能浅析
前景提要: 编写程序时,也许你不经意间,就不知不觉的定义了错误的类型,从而发生了额外的性能消耗,从而降低了效率,不要说就发生那么一次两次,如果说是程序中发生了循环.网络程序(不断请求处理的)等这些时候 ...
- .NET Core CSharp 中级篇 2-1 装箱与拆箱
.NET Core CSharp 中级篇 2-1 本节内容为装箱与拆箱 简介 装箱和拆箱是一个相对抽象的概念.你可以想象一下一堆满载货物的大卡车,他是由许多工人将货物集中堆放装入的,对于我们而言在没有 ...
- java基础第十一篇之Date、Math、自动装箱和拆箱
Date类 表示一个瞬间,就是一个时刻 * * 构造方法: * public Date();//创建一个表示当前系统时间的Date对象 * public Date(long time);//毫秒值,距 ...
- 一、基础篇--1.1Java基础-包装类的装箱和拆箱
包装类:java是典型的面向对象编程,但是八种基本数据类型并不支持面向对象编程.基本类型的数据不具备对象的特性,没有属性和方法.沿用它们只是为了迎合人类根深蒂固的习惯,并的确能简单.有效地进行常规数据 ...
- [CLR via C#]5.3 值类型的装箱和拆箱
原文:[CLR via C#]5.3 值类型的装箱和拆箱 在CLR中为了将一个值类型转换成一个引用类型,要使用一个名为装箱的机制. 下面总结了对值类型的一个实例进行装箱操作时内部发生的事: 1)在托管 ...
- 6个重要的.NET概念: - 堆栈,堆,值类型,引用类型,装箱和拆箱(转)
今天在Code Project上面看到一篇文章<6 important .NET concepts: - Stack, heap, Value types, reference types, b ...
- 基础系列(4)—— C#装箱和拆箱
一 装箱和拆箱的概念 装箱是将值类型转换为引用类型 : 拆箱是将引用类型转换为值类型 : 值类型:包括原类型(Sbyte.Byte.Short.Ushort.Int.Uint.Long.Ulong.C ...
- [C#] 类型学习笔记一:CLR中的类型,装箱和拆箱
在学习.NET的时候,因为一些疑问,让我打算把.NET的类型篇做一个总结.总结以三篇博文的形式呈现. 这篇博文,作为三篇博文的第一篇,主要探讨了.NET Framework中的基本类型,以及这些类型一 ...
- C#装箱与拆箱的研究
在对这个问题展开讨论之前,我们不妨先来问这么几个问题,以系统的了解我们今天要探究的主题. 观者也许曾无数次的使用过诸如System.Console类或.NET类库中那些品种繁多的类.那么,我想问的是它 ...
随机推荐
- SharpGL学习笔记(十七) 立体文字和平面文字
在写有关文字的主题前,笔者翻阅了几本书上的相关章节,研究了几天无果. 徐明亮<OpenGL游戏编程>书中介绍的是“位图字体”,也就是把字体栅格化,然后画出来.照着书上的VC代码翻译为C#的 ...
- ArcGIS制图之Sub Points点抽稀
简介 Sub Points工具是 Esri 中国自主开发的一个插件,该工具优先考虑点在空间分布上的均匀合理性,并结合点数据中包含的 "优先级" 属性进行筛选.通过获取每个点在一定范 ...
- SharePoint 2013 术语和术语集介绍
托管元数据是一个集中管理的术语的分层集合,我们可以定义术语和术语集,然后将其用作 SharePoint Server 2013 中项目的属性.简单的说,术语是一个可与 SharePoint Serve ...
- [android] 短信发送器
/*****************2016年4月23日 更新********************************/ 知乎:什么是 7 位元的字符? 英文字符难道不是 8 bit 是一个字 ...
- Android学习一(入门)
一.Android 系统简介 1.1.1G-4G 1G:模拟制式手机,1995年问世的第一代模拟制式手机,只能进行语音通话, 2G:手机使用GSM,CDMA(9K/s),增加了接收数据的功能 2.5G ...
- MySQL学习总结(摘抄)
1.数据库概述 简 而言之,数据库(DataBase)就是一个存储数据的仓库.为了方便数据的存储和管理,将数据按照特定的规律存储在磁盘上.通过数据库管理系统,可以有 效的组织和管理存储在数据库中的数据 ...
- CentOS 6.4安装配置LAMP服务器(Apache+PHP5+MySQL)
这篇文章主要介绍了CentOS 6.4安装配置LAMP服务器(Apache+PHP5+MySQL)的方法,需要的朋友可以参考下 文章写的不错,很详细:IDO转载自网络: 准备篇: 1.配置防火墙,开启 ...
- python简单的爬虫,网页图片
1 #!/usr/bin/python 2 #coding=utf-8 3 import urllib 4 import re 5 6 def gethtml(url): 7 page=urllib. ...
- 通过代码实现gz压缩,并保持原来的文件名
写这篇博客主要是为了记录一下这两天来的研究成果-gz带原有文件名压缩.首先要说的是这个解决方案不是通过调用命令gzip来做的,而是通过java代码来实现的,其中用到了apache的common com ...
- LightSpeed 相关问题处理
1. 关于KeyTable 配置文件中有一个节点 lightSpeedContexts 该节点下存放的是一些使用LightSpeed的配置,如 <add name="myDB&quo ...