为什么在c#中需要装箱和拆箱?
我知道什么是装箱和开箱,但我不理解它的真正用途。为什么,在哪里使用?
short s = 25;
object objshort = s; //Boxing
short anothershort = (short)objshort; //Unboxing
为什么在c#中需要装箱和拆箱?
我知道什么是装箱和开箱,但我不理解它的真正用途。为什么,在哪里使用?
short s = 25;
object objshort = s; //Boxing
short anothershort = (short)objshort; //Unboxing
当前回答
当一个方法只接受一个引用类型作为参数时(比如一个泛型方法通过new约束被约束为一个类),您将不能将引用类型传递给它,而必须对它进行装箱。
对于任何以object作为参数的方法也是如此——这必须是引用类型。
其他回答
当一个方法只接受一个引用类型作为参数时(比如一个泛型方法通过new约束被约束为一个类),您将不能将引用类型传递给它,而必须对它进行装箱。
对于任何以object作为参数的方法也是如此——这必须是引用类型。
理解这一点的最好方法是看看c#所基于的底层编程语言。
In the lowest-level languages like C, all variables go one place: The Stack. Each time you declare a variable it goes on the Stack. They can only be primitive values, like a bool, a byte, a 32-bit int, a 32-bit uint, etc. The Stack is both simple and fast. As variables are added they just go one on top of another, so the first you declare sits at say, 0x00, the next at 0x01, the next at 0x02 in RAM, etc. In addition, variables are often pre-addressed at compile-time, so their address is known before you even run the program.
In the next level up, like C++, a second memory structure called the Heap is introduced. You still mostly live in the Stack, but special ints called Pointers can be added to the Stack, that store the memory address for the first byte of an Object, and that Object lives in the Heap. The Heap is kind of a mess and somewhat expensive to maintain, because unlike Stack variables they don't pile linearly up and then down as a program executes. They can come and go in no particular sequence, and they can grow and shrink.
处理指针是很困难的。它们是导致内存泄漏、缓冲区溢出和失败的原因。c#来拯救。
在更高的层次上,c#,你不需要考虑指针——. net框架(用c++编写)为你考虑这些,并将它们作为对象引用呈现给你,为了性能,让你将更简单的值,如bool, bytes和int作为值类型存储。在底层,对象和实例化类的东西放在昂贵的内存管理堆上,而值类型放在低级C中相同的堆栈中——超快。
从编码器的角度来看,为了保持这两个根本不同的内存概念(和存储策略)之间的交互简单,值类型可以在任何时候被装箱。装箱会导致从堆栈中复制值,放入一个对象中,然后放在堆上——代价更大,但与引用世界的流动交互。正如其他答案指出的那样,当你说:
bool b = false; // Cheap, on Stack
object o = b; // Legal, easy to code, but complex - Boxing!
bool b2 = (bool)o; // Unboxing!
Boxing的优点的一个强有力的例子是检查null:
if (b == null) // Will not compile - bools can't be null
if (o == null) // Will compile and always return false
从技术上讲,对象o是堆栈中的一个地址,指向已复制到堆中的bool b的副本。我们可以检查o是否为空,因为bool值已经被装箱放在那里了。
一般来说,你应该避免装箱,除非你需要它,例如传递一个int/bool/任何作为对象的参数。. net中的一些基本结构仍然需要将值类型作为对象传递(因此需要Box),但在大多数情况下,您不应该需要Box。
一个不详尽的历史c#结构列表,需要Boxing,你应该避免:
The Event system turns out to have a Race Condition in naive use of it, and it doesn't support async. Add in the Boxing problem and it should probably be avoided. (You could replace it for example with an async event system that uses Generics.) The old Threading and Timer models forced a Box on their parameters but have been replaced by async/await which are far cleaner and more efficient. The .Net 1.1 Collections relied entirely on Boxing, because they came before Generics. These are still kicking around in System.Collections. In any new code you should be using the Collections from System.Collections.Generic, which in addition to avoiding Boxing also provide you with stronger type-safety.
你应该避免声明或传递你的值类型作为对象,除非你必须处理上述强制装箱的历史问题,并且你想避免在知道它无论如何都会被装箱时对它进行装箱的性能影响。
根据Mikael的建议:
这样做
using System.Collections.Generic;
var employeeCount = 5;
var list = new List<int>(10);
不是这个
using System.Collections;
Int32 employeeCount = 5;
var list = new ArrayList(10);
更新
这个答案最初认为Int32、Bool等会导致装箱,而实际上它们只是值类型的别名。也就是说,. net有Bool, Int32, String这样的类型,c#将它们别名为Bool, int, String,没有任何功能上的区别。
最后一个我不得不开箱的地方是从数据库中检索数据的代码(我没有使用LINQ to SQL,只是简单的旧ADO.NET):
int myIntValue = (int)reader["MyIntValue"];
基本上,如果使用泛型之前的旧api,就会遇到装箱。除此之外,这种情况并不常见。
装箱并不是你真正使用的东西——它是运行时使用的东西,这样你就可以在必要时以同样的方式处理引用和值类型。例如,如果您使用ArrayList来保存整数列表,则整数将被装箱以适应ArrayList中的对象类型插槽。
现在使用泛型集合,这种情况几乎消失了。如果你创建一个List<int>,没有装箱完成- List<int>可以直接保存整数。
一般来说,您通常会希望避免对值类型进行装箱。
然而,在极少数情况下,这是有用的。例如,如果您需要以1.1框架为目标,那么您将无法访问泛型集合。在. net 1.1中使用任何集合都需要将您的值类型视为系统。对象,该对象导致装箱/解装箱。
There are still cases for this to be useful in .NET 2.0+. Any time you want to take advantage of the fact that all types, including value types, can be treated as an object directly, you may need to use boxing/unboxing. This can be handy at times, since it allows you to save any type in a collection (by using object instead of T in a generic collection), but in general, it is better to avoid this, as you're losing type safety. The one case where boxing frequently occurs, though, is when you're using Reflection - many of the calls in reflection will require boxing/unboxing when working with value types, since the type is not known in advance.