为什么在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
当前回答
In .net, every instance of Object, or any type derived therefrom, includes a data structure which contains information about its type. "Real" value types in .net do not contain any such information. To allow data in value types to be manipulated by routines that expect to receive types derived from object, the system automatically defines for each value type a corresponding class type with the same members and fields. Boxing creates a new instances of this class type, copying the fields from a value type instance. Unboxing copies the fields from an instance of the class type to an instance of the value type. All of the class types which are created from value types are derived from the ironically named class ValueType (which, despite its name, is actually a reference type).
其他回答
为什么
拥有统一的类型系统,并允许值类型以与引用类型完全不同的方式表示其基础数据(例如,int只是一个32位的桶,这与引用类型完全不同)。
这样想。你有一个object类型的变量o。现在你有一个int型,你想把它变成o。o是对某处某物的引用,而int型显然不是对某处某物的引用(毕竟,它只是一个数字)。所以,你要做的是:创建一个可以存储int的新对象,然后将该对象的引用赋值给o。我们称这个过程为“装箱”。
所以,如果你不关心是否有一个统一的类型系统(例如,引用类型和值类型有非常不同的表示,你不想用一种通用的方式来“表示”这两者),那么你就不需要装箱。如果你不关心用int表示它们的底层值(也就是说,用int作为引用类型,只存储对它们底层值的引用),那么你不需要装箱。
我应该在哪里使用它。
例如,旧的集合类型ArrayList只包含对象。也就是说,它只存储对某个地方的某个对象的引用。如果没有装箱,就不能在这样的集合中放入int型。但在拳击中,你可以。
现在,在泛型的时代,你并不真的需要这个,通常可以轻松地进行,而不需要考虑这个问题。但有一些注意事项需要注意:
这是正确的:
double e = 2.718281828459045;
int ee = (int)e;
这不是:
double e = 2.718281828459045;
object o = e; // box
int ee = (int)o; // runtime exception
相反,你必须这样做:
double e = 2.718281828459045;
object o = e; // box
int ee = (int)(double)o;
首先,我们必须显式地打开double ((double)o),然后将其强制转换为int型。
下面的结果是什么:
double e = 2.718281828459045;
double d = e;
object o1 = d;
object o2 = e;
Console.WriteLine(d == e);
Console.WriteLine(o1 == o2);
下句话之前先想一下。
如果你说真假太棒了!等等,什么?这是因为引用类型上的==使用引用相等来检查引用是否相等,而不是检查底层值是否相等。这是一个很容易犯的危险错误。也许更微妙
double e = 2.718281828459045;
object o1 = e;
object o2 = e;
Console.WriteLine(o1 == o2);
也会打印False!
更好的说法是:
Console.WriteLine(o1.Equals(o2));
然后,谢天谢地,它会打印True。
最后一个微妙之处:
[struct|class] Point {
public int x, y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
}
Point p = new Point(1, 1);
object o = p;
p.x = 2;
Console.WriteLine(((Point)o).x);
输出是什么?视情况而定!如果Point是一个结构体,则输出为1,但如果Point是一个类,则输出为2!装箱转换生成被装箱的值的副本,以解释行为上的差异。
当一个方法只接受一个引用类型作为参数时(比如一个泛型方法通过new约束被约束为一个类),您将不能将引用类型传递给它,而必须对它进行装箱。
对于任何以object作为参数的方法也是如此——这必须是引用类型。
Boxing和Unboxing专门用于将值类型对象视为引用类型;将它们的实际值移动到托管堆并通过引用访问它们的值。
如果不进行装箱和拆箱,就无法通过引用传递值类型;这意味着您不能将值类型作为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,没有任何功能上的区别。
In .net, every instance of Object, or any type derived therefrom, includes a data structure which contains information about its type. "Real" value types in .net do not contain any such information. To allow data in value types to be manipulated by routines that expect to receive types derived from object, the system automatically defines for each value type a corresponding class type with the same members and fields. Boxing creates a new instances of this class type, copying the fields from a value type instance. Unboxing copies the fields from an instance of the class type to an instance of the value type. All of the class types which are created from value types are derived from the ironically named class ValueType (which, despite its name, is actually a reference type).