搞不c#java自动装箱和拆箱拆箱什么怎么写

 上传我的文档
 下载
 收藏
该文档贡献者很忙,什么也没留下。
 下载此文档
正在努力加载中...
关于C_装箱与拆箱的理解
下载积分:30
内容提示:关于C_装箱与拆箱的理解
文档格式:PDF|
浏览次数:82|
上传日期: 05:46:16|
文档星级:
该用户还上传了这些文档
关于C_装箱与拆箱的理解
官方公共微信博客访问: 956300
博文数量: 7990
注册时间:
鏆傛棤浠嬬粛
ITPUB论坛APP
ITPUB论坛APP
APP发帖 享双倍积分
IT168企业级官微
微信号:IT168qiye
系统架构师大会
微信号:SACC2013
分类: 微软技术
C#学习笔记二:用实例深入理解装箱、拆箱 (转)[@more@]学习的过程中,发现大多数的书都讲到了装箱(boxing)和拆箱(unboxing)的概念,至于为什么要理解装箱和拆箱?则总是一句话带过:的云云。至于为什么会对程序的性能产生影响,如何影响,我总感觉讲得并不透彻,当然也可能是我理解力有限。
:namespace prefix = o ns = "urn:schemas--com::office" />
这篇笔记,我并不打算对装箱和拆箱做全面的介绍,这些内容书上都有,csdn上也有很好的文章(请见kenli写的学习笔记一 - 装箱拆箱
?Id=19575">http://www.csdn.net/Develop/Read_Article.asp?Id=19575),我只做简单的总结,并在此基础上引入两个例子,一个例子用ILDASM.EXE查看装箱和拆箱的过程,另外一个例子我编制一个简单例子分析正确理解装箱和拆箱对程序性能的影响。
由于在下面的例子和以后的例子我们将再次用到ILDASM,但不再给出详细的解释,因此给出MSDN关于反语言的帮助信息,要查找汇编语言的命令,请在MSDN中.NET /参考/类库/ System.Reflection.Emit 命名空间/OpCodes类中可以找到相关信息。
总结1:.NET中所有类型都是,所有类型的根是System.。
总结2:类型分为值类型(value)和引用类型(regerence type)。中定义的值类型包括:原类型(Sbyte、Byte、Short、Ushort、Int、Uint、Long、Ulong、Char、Float、Double、Bool、Decimal)、枚举(enum)、结构(struct)。引用类型包括:类、数组、接口、委托、字符串等。
实例一:读下列程序,你能说出其中进行了几次装箱和拆箱的操作吗?
class sample1
& public static void Main()
& int i=10;
& object obj=i;
& Console.WriteLine(i+","+(int)obj);
其中发生了三次装箱操作和一次拆箱操作。第一次object obj=i;将i装箱;而Console.WriteLine方法用的参数是String对象,因此,i+","+(int)obj中,i需要进行一次装箱(转换成String对象),(int)obj将obj对象拆箱成值类型,而根据WriteLine方法,比较将(int)obj值装箱成引用类型。说起来这么复杂,大家看看ildasm.exe的反汇编结果(如下图),就很容易理解了。
注意图中红色圆圈的标识。
2003-9-.jpg" o:title="图21">
如果我们将Console.WriteLine(i+","+(int)obj);
改为:& Console.WriteLine(obj+","+obj); 得到同样的效果,而其中仅进行一次装箱操作(object obj=i;),虽然这个程序并没有实际的意义,但是加深我们对概念的理解。
实例二:我这里我列出两个例子,装箱和拆箱对程序性能的影响不问自知。我的机器是P4 1.6A,512M。随后会列出测试的截图,你比我更快吗?当然是的?那么告诉我吧。:(
// 例子1:boxing1.cs
using System.C
namespace test1
class Class1
static void Main(string[] args)
DateTime startTime = DateTime.N
ArrayList myArrayList = new ArrayList();
// 重复5次测试
for(int i = 5; i > 0; i--)
myArrayList.Clear();
// 将值类型加入myArrayList数组
for(count = 0; count < 5000000; count++)
myArrayList.Add(count); //装箱
// 重新得到值
for(count = 0; count < 5000000; count++)
j = (int) myArrayList[count];& //拆箱
// 打印结果
DateTime endTime = DateTime.N
Console.WriteLine("Start: {0}
Spend: {2}",
startTime, endTime, endTime-startTime);
Console.WriteLine("Push ENTER to return commandline...");
Console.ReadLine();
下图是boxing1.exe的测试结果:
// 例子2:boxing2.cs
using System.C
namespace test2
class Class2
static void Main(string[] args)
ArrayList myArrayList = new ArrayList();
// 构造 5000000 字符串数组
string [] strList = new string[5000000];
for(count = 0; count < 5000000; count++)
strList[count] = count.ToString();
// 重复5次测试
DateTime startTime = DateTime.N
for(int&i = 5;&i > 0; i--)
myArrayList.Clear();
// 将值类型加入myArrayList数组
for(count = 0; count < 5000000; count++)
myArrayList.Add(strList[count]);
// 重新得到值
for(count = 0; count < 5000000; count++)
s = (string) myArrayList[count];
// 打印结果
DateTime endTime = DateTime.N
Console.WriteLine("Start: {0}
Spend: {2}",
startTime, endTime, endTime-startTime);
Console.WriteLine("Push ENTER to return commandline...");
Console.ReadLine();
下图是boxing2.exe的测试结果:
实例二说明:boxing1.cs的循环中包含一次装箱和一次拆箱(这里我均忽略两个程序打印时的装箱操作),boxing2.cs则没有相应的操作。当循环次数足够大的时候,性能差异是明显的。再次提醒你别忘了ILDASM.EXE这个工具哦,分别看看,才能一窥程序的本质。否则,粗看程序boxing2.cs比boxing1.cs多了不少代码,更多了一个M)的循环,就以为boxing2会更慢。。。
另外一方面,装箱和拆箱对性能的影响更偏重于大型的程序和,这就是我用这么多循环的原因。但你能保证你不会进行大批量的数据处理吗?
MSDN上有更实用的例子:统计大量的英文单词,当然也更加复杂,故不在此详细讲解。
.asp">/china/msdn/voices/csharp.asp
文章的结尾处,我想你应该测试一下你对装箱和拆箱的理解:(同样来自MSDN)
看看各种方案中是否进行了装箱和拆箱的操作,各有多少次。
int total = 35;
DateTime date = DateTime.N
string s = String.Format("Your total was {0} on {1}", total, date);
Hashtable t = new Hashtable();
t.Add(0, "zero");
t.Add(1, "one");
DateTime d = DateTime.N
String s = d.ToString();
int[] a = new int[2];
a[0] = 33;
ArrayList a = new ArrayList();
a.Add(33);
MyStruct s = new MyStruct(15);
IProcess = (IProcess)
ip.Process();
今天就到这里吧,我也是初学,望多多指教。什么?上面测试的标答?呵呵,你应该找得到的,找不到?我会贴在评论中。
阅读(269) | 评论(0) | 转发(0) |
相关热门文章
给主人留下些什么吧!~~
请登录后评论。知识点 &值类型。 && 值类型是在栈中分配内存,在声明时初始化才能使用,不能为null。 &&
值类型超出作用范围系统自动释放内存。 && 主要由两类组成:结构,枚举(enum),结构分为以下几类: &&&
1、整型(Sbyte、Byte、Char、Short、Ushort、Int、Uint、Long、Ulong) &&&
2、浮点型(Float、Double) &&& 3、decimal &&& 4、bool &&& 5、用户定义的结构(struct)
&引用类型。 && 引用类型在堆中分配内存,初始化时默认为null。 && 引用类型是通过垃圾回收机制进行回收。 &&
包括类、接口、委托、数组以及内置引用类型object与string。 概念&&
由于C#中所有的数据类型都是由基类System.Object继承而来的,所以值类型和引用类型的值可以通过显式
(或隐式)操作相互转换,而这转换过程也就是装箱(boxing)和拆箱(unboxing)过程。
装箱&& 是值类型到 object 类型或到此值类型所实现的任何接口类型的隐式转换。对值类型装箱会在堆中分配一
个对象实例,并将该值复制到新的对象中。拆箱&& 是从 object 类型到值类型或从接口类型到实现该接口的值类型的显式转换。
-------------------
为何需要装箱? 一种最普通的场景是,调用一个含类型为Object的参数的方法,该Object可支持任意为型,以便通用。当你需
要将一个值类型(如Int32)传入时,需要装箱。
另一种用法是,一个非泛型的容器,同样是为了保证通用,而将元素类型定义为Object。于是,要将值类型数据
加入容器时,需要装箱。
装箱的内部操作。 装箱: 对值类型在堆中分配一个对象实例,并将该值复制到新的对象中。按三步进行。 &
第一步:新分配托管堆内存(大小为值类型实例大小加上一个方法表指针和一个SyncBlockIndex)。 &
第二步:将值类型的实例字段拷贝到新分配的内存中。 & 第三步:返回托管堆中新分配对象的地址。这个地址就是一个指向对象的引用了。
拆箱:检查对象实例,确保它是给定值类型的一个装箱值。将该值从实例复制到值类型变量
装箱/拆箱对执行效率的影响(如何优化效率)装箱时,生成的是全新的引用对象,这会有时间损耗,也就是造成效率降低。 那该如何做呢?
避免装箱的方法:& 1、通过重载函数来避免。& 2、通过泛型来避免。&&
凡事并不能绝对,假设你想改造的代码为第三方程序集,你无法更改,那你只能是装箱了
。 对于装箱/拆箱代码的优化,由于C#中对装箱和拆箱都是隐式的,所以,根本的方法是对
代码进行分析,而分析最直接的方式是了解原理结何查看反编译的IL代码。比如:在循环体
中可能存在多余的装箱,你可以简单采用提前装箱方式进行优化。
对装箱/拆箱更进一步的了解 装箱/拆箱并不如上面所讲那么简单明了,比如:装箱时,变为引用对象,会多出一个方法表指针,这会有何用处呢?
通过示例来进一步探讨。 例子: & Struct A : ICloneable & { &&& public Int32
&&& public override String ToString() &&& { &&&&& return
String.Format(&{0}&,x); &&& }&&& public object Clone() &&& {
&&&&& return MemberwiseClone(); &&& } & } & static void main()
& { &&& A &&& a.x = 100; &&& Console.WriteLine(a.ToString());
&&& Console.WriteLine(a.GetType()); &&& A a2 = (A)a.Clone(); &&&
ICloneable c = a2; Ojbect o = c.Clone(); &
}&:a.ToString()。编译器发现A重写了ToString方法,会直接调用ToString的指令。因为A是值类型,编译器不会出现多态行为。因此,直接调用,不装箱。(注:ToString是A的基类System.ValueType的方法)&&:a.GetType(),GetType是继承于System.ValueType的方法,要调用它,需要一个方法表指针,于是a将被装箱,从而生成方法表指针,调用基类的System.ValueType。(补一句,所有的值类型都是继承于System.ValueType的)。&&:a.Clone(),因为A实现了Clone方法,所以无需装箱。
5.3:ICloneable转型:当a2为转为接口类型时,必须装箱,因为接口是一种引用类型。&&:c.Clone()。无需装箱,在托管堆中对上一步已装箱的对象进行调用。
附:其实上面的基于一个根本的原理,因为未装箱的值类型没有方法表指针,所以,不能通过值类型来调用其上继承的虚方法。另外,接口类型是一个引用类型。对此,我的理解,该方法表指针类似C++的虚函数表指针,它是用来实现引用对象的多态机制的重要依据。
如何更改已装箱的对象?& 对于已装箱的对象,因为无法直接调用其指定方法,所以必须先拆箱,再调用方法,但再次拆箱,会生成新的栈
实例,而无法修改装箱对象。有点晕吧,感觉在说绕口令。还是举个例子来说:(在上例中追加change方法) && public void
Change(Int32 x) && {&&&& this.x = && } //调用: && A a = new
A(); && a.x = 100; && Object o = //装箱成o,下面,想改变o的值。 &&
((A)o).Change(200); //改掉了吗?没改掉。 没改掉的原因是o在拆箱时,生成的是临时的栈实例A,所以,改
动是基于临时A的,并未改到装箱对象。 (附:在托管C++中,允许直接取加拆箱时第一步得到的实例引用,而直接更改,但C#不行。) 那该如何是好?
通过接口方式,可以达到相同的效果。 实现如下: & interface IChange & { &&& void
Change(Int32 x); & } & struct A : IChange & { && & & }
//调用:& ((IChange)o).Change(200);//改掉了吗?改掉了。 为啥现在可以改?
在将o转型为IChange时,这里不会进行再
次装箱,当然更不会拆箱,因为o已经是引用类型,再因为它是IChange类型,所以可以直接调用Change,于是,更
改的也就是已装箱对象中的字段了,达到期望的效果。&
-------------------------------------------------------------------------------------------------------------&
-------------------------------------------------------------------------------------------------------------&
-------------------------------------------------------------------------------------------------------------&
附录:(转自和
&&&&&&&&& )&a、装箱&&&&
一个很简单的例子。新建一个控制台程序,在Main()里面就写两句话。&&& &&&& &&&& int i = 13;&&&&
object ob =&&&&& 编译。然后用.net 提供的工具ILDASM.exe(MSIL Disassembler
)查看新生产这个程序的配件代码(Microsoft intermediate language ,MSIL。顺带说一句.net framework
SDK除了这个MSIL的反汇编工具,当然还提供了汇编工具ILASM.exe,可以使用MSIL编写程序,当然。。谁也不会没事这么干。那个反汇编工具倒是挺有用,可以了解一些底层机制)&&&&&
用那个工具查看一下编译后程序的Main(string[] args)方法,显示如下(我现在用的时.net framework
2.0可能MSIL代码显示出来的和原来的1.0或者1.1稍有不同,不过没关系核心没变):.method private hidebysig static
void& Main(string[] args) cil managed{& .entrypoint& // Code
size&&&&&& 12 (0xc)& .maxstack& 1& .locals init ([0] int32
i,&&&&&&&&&& [1] object ob)& IL_0000:& nop& IL_0001:& ldc.i4.s&&
13& IL_0003:& stloc.0& IL_0004:& ldloc.0& IL_0005:& box&&&&&&&
[mscorlib]System.Int32& IL_000a:& stloc.1& IL_000b:& ret} // end of
method Program::Main稍微解释一下:&(1)先注意& .locals ,定义了两个类型分别为int32 和object
的局部变量&(2)然后看&& IL_0001处,ldc是个指令,后面的i4.s指出作为32位(4个字节)整数被压入堆栈。而压入的值就是13&&
&(3)下面的stloc把上面的值从堆栈弹出给局部变量i,这里的.0是指弹出给到第一个局部变量中,也就是i了&(4)这个值(13),被弹出后,就被装载回堆栈,也就是后面IL_0004行的ldloc命令做的事情&(5)然后使用CIL(Common
Language Infrastructure
)box将这个值转换为引用类型。装箱喽~&(6)stloc.1根据(3)的解释就好理解了,就是把box返回值弹出给第二个局部变量ob中。&&
但是这个box指令内部又发生了什么呢?有牛人告诉了我们。&
(1)在堆上分配内存。因为值类型最终有一个对象代表,所有堆上分配的内存量必须是值类型的大小加上容纳此对象及其内部结构(比如虚拟方法表)所需的内存量。&
(2)值类型的值被复制到新近分配的内存中& (3)新近分配的对象地址被放到堆栈上,现在它指向一个引用类型&&& b、拆箱&&
在刚才程序的基础上,再加一句话变成,编译:&&&&&&&&&&& int i = 13;&&&&&&&&&&& object ob =
i;&&&&&&&&&&& int j = (int)&&
在装箱的时候,并不需要显示类型转换。但在拆箱时需要类型转换。这是因为在拆箱时对象可以被转换为任何类型。看看MSIL代码变成这德行了:.method
private hidebysig static void& Main(string[] args) cil managed{&
.entrypoint& // Code size&&&&&& 19 (0x13)& .maxstack& 1& .locals
init ([0] int32 i,&&&&&&&&&& [1] object ob,&&&&&&&&&& [2] int32 j)&
IL_0000:& nop& IL_0001:& ldc.i4.s&& 13& IL_0003:& stloc.0& IL_0004:&
ldloc.0& IL_0005:& box&&&&&&& [mscorlib]System.Int32& IL_000a:&
stloc.1& IL_000b:& ldloc.1& IL_000c:& unbox.any&
[mscorlib]System.Int32& IL_0011:& stloc.2& IL_0012:& ret} // end of
method Program::Main&&&& 整个流程就不再重复叙述了,参照前面的解释现在这个过程应该能看明白。&&&
说说拆箱unbox的内部过程:&& (1)因为一个对象将被转换,所以编译器必须先判断堆栈上指向合法对象的地址,以及这个对象类型是否可以转换为MSL
unbox指令调用中指定的值类型。如果检查失败就抛出InvalidCastException异常。&&
(2)校验通过后,就返回指向对象内的值的指针。可以看出,装箱操作会创建转换类型的副本,而拆箱就不会。不过注意一下,在我们装箱的时候是先把变量i的值复制了一份赋给ob的,所变量j拿到的是ob这个变量的引用。也就是后面再改变i的值并不会影响j的值,但是改变ob的值就会。&&&
c、再来一个稍微复杂点的例子,有如下代码:&&&&&&&&&&& int i = 13;&&&&&&&&&&& object ob =
i;&&&&&&&&&&& Console.WriteLine(i + "," + (Int32)ob);&&&
这里做了几次装箱和拆箱操作呢?我开始想当然的以为是1次装1次拆箱操作了,可实际上确是3次装箱1次拆箱操作!先看看MSIL代码:.method
private hidebysig static void& Main(string[] args) cil managed{&
.entrypoint& // Code size&&&&&& 45 (0x2d)& .maxstack& 3& .locals
init ([0] int32 i,&&&&&&&&&& [1] object ob)& IL_0000:& nop&
IL_0001:& ldc.i4.s&& 13& IL_0003:& stloc.0& IL_0004:& ldloc.0&
IL_0005:& box&&&&&&& [mscorlib]System.Int32& IL_000a:& stloc.1&
IL_000b:& ldloc.0& IL_000c:& box&&&&&&& [mscorlib]System.Int32&
IL_0011:& ldstr&&&&& ","& IL_0016:& ldloc.1& IL_0017:& unbox.any&
[mscorlib]System.Int32& IL_001c:& box&&&&&&& [mscorlib]System.Int32&
IL_0021:& call&&&&&& string
[mscorlib]System.String::Concat(object,&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
object,&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
object)& IL_0026:& call&&&&&& void
[mscorlib]System.Console::WriteLine(string)& IL_002b:& nop& IL_002c:&
ret} // end of method Program::Main&&&& (1)前面好说,跟前面一样& object ob =
i;引起了一次装箱操作也就是 IL_0005处代码。&&&
(2)后面可以看出Console.WriteLine方法调用的是单个String作为参数的版本。因此上面调用了String.Concat方法将i + "," +
(Int32)ob这3个值连接产生单个String再传给WriteLine。&&&
(3)String.Concat的重载版本里面找到最匹配的就是Concat(object,
object,object)。这样为了匹配这3个参数:&&&&&&& (3.1) IL_000c处代码,第一个参数i被装箱 &&&&&&&
(3.2)IL_0011 处ldstr&&&&& "," 就是将字符串','压入堆栈&&&&&&& (3.3)然后 IL_0017
(int32)ob引起了一次拆箱操作&&&&&&&
(3.4)我们可怜的(int32)ob,又为了匹配Concat的参数,再次被装箱(IL_001c)&&
明显后面那个(int32)ob造成了一次不必要的拆箱和装箱操作!所以正因为.net的自动类型处理能力,还是小心地注意一下写法,否则就会引起不必有的性能损失。&&&
下面举类似的小例子&&& 还是个那个控制台代码写成这样&&&&&&& static ArrayL&&&&&&&
static void Main(string[] args)&&&&&&& {&&&&&&&&&&& int i =
13;&&&&&&&&&&& al = new ArrayList();&&&&&&&&&&&
al.Add(i);&&&&&&&&&&& Console.WriteLine("{0}", i);&&&&&&&&&&&
&&&&&&& } & MSIL命令如下:.method private hidebysig static void&
Main(string[] args) cil managed{& .entrypoint& // Code size&&&&&& 49
(0x31)& .maxstack& 2& .locals init ([0] int32 i)& IL_0000:& nop&
IL_0001:& ldc.i4.s&& 13& IL_0003:& stloc.0& IL_0004:& newobj&&&&
instance void [mscorlib]System.Collections.ArrayList::.ctor()& IL_0009:&
stsfld&&&& class [mscorlib]System.Collections.ArrayList
ConsoleApplication1.Program::al& IL_000e:& ldsfld&&&& class
[mscorlib]System.Collections.ArrayList ConsoleApplication1.Program::al&
IL_0013:& ldloc.0& IL_0014:& box&&&&&&& [mscorlib]System.Int32&
IL_0019:& callvirt&& instance int32
[mscorlib]System.Collections.ArrayList::Add(object)& IL_001e:& pop&
IL_001f:& ldstr&&&&& "{0}"& IL_0024:& ldloc.0& IL_0025:& box&&&&&&&
[mscorlib]System.Int32& IL_002a:& call&&&&&& void
[mscorlib]System.Console::WriteLine(string,&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
object)& IL_002f:& nop& IL_0030:& ret} // end of method
Program::Main&其他的都不用管,看懂了前面我说的,那么这里就知道因为ArrayList.Add(object)做了一次装箱和Console.WriteLine(string,object)又做了一次装箱。如果我们换一种写法,把程序改成这样:&&&&&&&&
static ArrayL&&&&&&& static void Main(string[] args)&&&&&&&
{&&&&&&&&&&& int i = 13;&&&&&&&&&&& object ob =&&&&&&&&&&& al =
new ArrayList();&&&&&&&&&&& al.Add(ob);&&&&&&&&&&&
Console.WriteLine("{0}", ob);&&&&&&&&&&& &&&&&&&
}&MSIL就变成:.method private hidebysig static void& Main(string[] args)
cil managed{& .entrypoint& // Code size&&&&&& 46 (0x2e)&
.maxstack& 2& .locals init ([0] int32 i,&&&&&&&&&& [1] object ob)&
IL_0000:& nop& IL_0001:& ldc.i4.s&& 13& IL_0003:& stloc.0& IL_0004:&
ldloc.0& IL_0005:& box&&&&&&& [mscorlib]System.Int32& IL_000a:&
stloc.1& IL_000b:& newobj&&&& instance void
[mscorlib]System.Collections.ArrayList::.ctor()& IL_0010:& stsfld&&&& class
[mscorlib]System.Collections.ArrayList ConsoleApplication1.Program::al&
IL_0015:& ldsfld&&&& class [mscorlib]System.Collections.ArrayList
ConsoleApplication1.Program::al& IL_001a:& ldloc.1& IL_001b:& callvirt&&
instance int32 [mscorlib]System.Collections.ArrayList::Add(object)&
IL_0020:& pop& IL_0021:& ldstr&&&&& "{0}"& IL_0026:& ldloc.1&
IL_0027:& call&&&&&& void
[mscorlib]System.Console::WriteLine(string,&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
object)& IL_002c:& nop& IL_002d:& ret} // end of method
Program::Main
阅读(...) 评论()

我要回帖

更多关于 拆箱和装箱 的文章

 

随机推荐