Home > Article > Backend Development > C# boxing and unboxing knowledge compilation
1. Boxing and unboxing are abstract concepts
2. Boxing converts value types into reference types;
Unboxing converts reference types into value types
Using the boxing and unboxing functions, value types and reference types can be linked by allowing any value of the value type to be converted to and from the Object type.
For example:
int val = 100; object obj = val; Console.WriteLine (“对象的值 = {0}", obj);
This is a process of boxing, which is the process of converting a value type to a reference type
int val = 100; object obj = val; int num = (int) obj; Console.WriteLine ("num: {0}", num);
This is an unboxing process, which is a process of converting a value type to a reference type, and then converting a reference type to a value type Process
Note: Only boxed objects can be unboxed
3. In .NET, data types are divided into value types and reference (not equivalent to C++ pointers) types, corresponding to this , memory allocation is divided into two ways, one is the stack, and the other is the heap (note: it is a managed heap)
Value types will only be allocated on the stack.
Reference types allocate memory and managed heap.
The managed heap corresponds to garbage collection.
4: What is boxing/unboxing?
Boxing: used to store value types in the garbage collection heap. Boxing is an implicit conversion of a value type to the object type or to any interface type that the value type implements.
Unboxing: Explicit conversion from an object type to a value type or from an interface type to a value type that implements the interface.
5: Why is packaging needed? (Why convert the value type to a reference type?)
One of the most common scenarios is to call a method with a parameter of type Object. The Object can support any type for universal use. When you need to pass in a value type (such as Int32), boxing is required.
Another usage is a non-generic container, also to ensure universality, the element type is defined as Object. Therefore, when adding value type data to a container, boxing is required.
6: Internal operations of boxing/unboxing
Boxing
Allocates an object instance in the heap for a value type and copies the value to a new object. Follow three steps.
Newly allocated managed heap memory (the size is the size of the value type instance plus a method table pointer and a SyncBlockIndex).
Copy the instance field of the value type to the newly allocated memory.
Returns the address of the newly allocated object in the managed heap. This address is a reference to the object.
Some people understand it this way: If Int32 is boxed, the returned address points to an Int32. I think it is not impossible to understand it this way, but it does have problems. Firstly, it is not comprehensive, and secondly, pointing to Int32 does not tell its essence (in the managed heap).
Unboxing
Check the object instance to ensure it is a boxed value of the given value type. Copies the value from the instance into a value type variable.
According to some books, unboxing only obtains the pointer to the value type part of the reference object, and content copying is the trigger of the assignment statement. I don't think it matters. The most critical thing is to check the nature of the object instance. The unboxing and boxing types must match. At this point, at the IL layer, I can’t see the principle. My guess is that maybe a method like GetType is called. Take out the type for matching (because strict matching is required).
7: The impact of boxing/unboxing on execution efficiency
Obviously, it can be seen from the principle that when boxing, a brand new reference object is generated, which will cause time loss. , that is, resulting in reduced efficiency.
How to do it?
First of all, boxing should be avoided as much as possible.
For example, both situations in Example 2 above can be avoided. In the first case, it can be avoided by overloading the function. The second case can be avoided through generics.
Of course, everything is not absolute. If the code you want to transform is a third-party assembly and you cannot change it, then you can only box it.
Regarding the optimization of boxing/unboxing code, since boxing and unboxing are implicit in C#, the fundamental method is to analyze the code, and the most direct way to analyze is to understand the principle structure. View the decompiled IL code.
For example: there may be redundant boxing in the loop body, you can simply use advance boxing for optimization.
8: Better understanding of packing/unboxing
Packing/unboxing is not as simple and clear as mentioned above
For example: when packing, change To reference an object, there will be an extra method table pointer. What is the use of this?
We can explore further through examples.
for example:
Struct A : ICloneable { public Int32 x; public override String ToString() { return String.Format(”{0}”,x); } public object Clone() { return MemberwiseClone(); } } static void main() { A 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方法,所以无需装箱。
ICloneable转型:当a2为转为接口类型时,必须装箱,因为接口是一种引用类型。
c.Clone()。无需装箱,在托管堆中对上一步已装箱的对象进行调用。
附:其实上面的基于一个根本的原理,因为未装箱的值类型没有方法表指针,所以,不能通过值类型来调用其上继承的虚方法。另外,接口类型是一个引用类型。对此,我的理解,该方法表指针类似C++的虚函数表指针,它是用来实现引用对象的多态机制的重要依据。
9:如何更改已装箱的对象
对于已装箱的对象,因为无法直接调用其指定方法,所以必须先拆箱,再调用方法,但再次拆箱,会生成新的栈实例,而无法修改装箱对象。有点晕吧,感觉在说绕口令。还是举个例子来说:(在上例中追加change方法)
public void Change(Int32 x) { this.x = x; }
调用:
A a = new A(); a.x = 100; Object o = a; //装箱成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,于是,更改的也就是已装箱对象中的字段了,达到期望的效果。
10、将值类型转换为引用类型,需要进行装箱操作(boxing):
首先从托管堆中为新生成的引用对象分配内存
然后将值类型的数据拷贝到刚刚分配的内存中
返回托管堆中新分配对象的地址
可以看出,进行一次装箱要进行分配内存和拷贝数据这两项比较影响性能的操作。
将引用类型转换为值类型,需要进行拆箱操作(unboxing):
首先获取托管堆中属于值类型那部分字段的地址,这一步是严格意义上的拆箱。
将引用对象中的值拷贝到位于线程堆栈上的值类型实例中。
经过这2步,可以认为是同boxing是互反操作。严格意义上的拆箱,并不影响性能,但伴随这之后的拷贝数据的操作就会同boxing操作中一样影响性能。
11、
NET的所有类型都是由基类System.Object继承过来的,包括最常用的基础类型:int, byte, short,bool等等,就是说所有的事物都是对象。
如果申明这些类型得时候都在堆(HEAP)中分配内存,会造成极低的效率!(个中原因以及关于堆和栈得区别会在另一篇里单独得说说!)
.NET如何解决这个问题得了?正是通过将类型分成值型(value)和引用型(regerencetype),
C#中定义的值类型和引用类型
值类型:原类型(Sbyte、Byte、Short、Ushort、Int、Uint、Long、Ulong、Char、Float、Double、Bool、Decimal)、枚举(enum)、结构(struct)
引用类型:类、数组、接口、委托、字符串等
值型就是在栈中分配内存,在申明的同时就初始化,以确保数据不为NULL;
引用型是在堆中分配内存,初始化为null,引用型是需要GARBAGE COLLECTION来回收内存的,值型不用,超出了作用范围,系统就会自动释放!
下面就来说装箱和拆箱的定义!
装箱就是隐式的将一个值型转换为引用型对象。比如:
int i=0; Syste.Object obj=i;
这个过程就是装箱!就是将i装箱!
拆箱就是将一个引用型对象转换成任意值型!比如:
int i=0; System.Object obj=i; int j=(int)obj;
这个过程前2句是将i装箱,后一句是将obj拆箱!
更多c#装箱和拆箱知识整理相关文章请关注PHP中文网!