本文翻译自:Why do we need boxing and unboxing in C#?
Why do we need boxing and unboxing in C#? 为什么我们需要在C#中装箱和拆箱?
I know what boxing and unboxing is, but I can't comprehend the real use of it. 我知道拳击和拆箱是什么,但我无法理解它的实际用途。 Why and where should I use it? 我应该在哪里以及在哪里使用它?
short s = 25;
object objshort = s; //Boxing
short anothershort = (short)objshort; //Unboxing
#1楼
参考:https://stackoom.com/question/8rOD/为什么我们需要在C-中装箱和拆箱
#2楼
In general, you typically will want to avoid boxing your value types. 通常,您通常希望避免装箱值类型。
However, there are rare occurances where this is useful. 但是,很少有这种情况有用。 If you need to target the 1.1 framework, for example, you will not have access to the generic collections. 例如,如果您需要定位1.1框架,则无法访问泛型集合。 Any use of the collections in .NET 1.1 would require treating your value type as a System.Object, which causes boxing/unboxing. 在.NET 1.1中使用集合需要将您的值类型视为System.Object,这会导致装箱/取消装箱。
There are still cases for this to be useful in .NET 2.0+. 仍有一些情况可以在.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. 这有时很方便,因为它允许您在集合中保存任何类型(通过在通用集合中使用对象而不是T),但一般来说,最好避免这种情况,因为您正在失去类型安全性。 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. 但是,经常发生拳击的一种情况是,当你使用反射时 - 反射中的许多调用在处理值类型时需要装箱/拆箱,因为事先不知道类型。
#3楼
Boxing and Unboxing are specifically used to treat value-type objects as reference-type; Boxing和Unboxing专门用于将值类型对象视为引用类型; moving their actual value to the managed heap and accessing their value by reference. 将其实际值移动到托管堆并通过引用访问它们的值。
Without boxing and unboxing you could never pass value-types by reference; 没有装箱和拆箱,你永远不能通过引用传递值类型; and that means you could not pass value-types as instances of Object. 这意味着您无法将值类型作为Object的实例传递。
#4楼
Why 为什么
To have a unified type system and allow value types to have a completely different representation of their underlying data from the way that reference types represent their underlying data (eg, an int
is just a bucket of thirty-two bits which is completely different than a reference type). 要有一个统一的类型系统,并允许值类型从引用类型表示其基础数据的方式中获得完全不同的基础数据表示(例如, int
只是一个32位的桶,与a完全不同参考类型)。
Think of it like this. 想想这样。 You have a variable o
of type object
. 你有一个类型object
的变量o
。 And now you have an int
and you want to put it into o
. 现在你有一个int
,你想把它放进o
。 o
is a reference to something somewhere, and the int
is emphatically not a reference to something somewhere (after all, it's just a number). o
是对某个地方的引用,而int
强调的不是对某个地方的引用(毕竟,它只是一个数字)。 So, what you do is this: you make a new object
that can store the int
and then you assign a reference to that object to o
. 所以,你要做的是:创建一个可以存储int
的新object
,然后将该对象的引用分配给o
。 We call this process "boxing." 我们称这个过程为“拳击”。
So, if you don't care about having a unified type system (ie, reference types and value types have very different representations and you don't want a common way to "represent" the two) then you don't need boxing. 所以,如果你不关心拥有统一的类型系统(即,引用类型和值类型具有非常不同的表示,并且你不想要一种“代表”两者的常用方法),那么你不需要装箱。 If you don't care about having int
represent their underlying value (ie, instead have int
be reference types too and just store a reference to their underlying value) then you don't need boxing. 如果你不关心int
表示它们的底层值(即, int
也是引用类型而只是存储对它们的基础值的引用)那么你不需要装箱。
where should I use it. 我应该在哪里使用它。
For example, the old collection type ArrayList
only eats object
s. 例如,旧的集合类型ArrayList
仅占用object
s。 That is, it only stores references to somethings that live somewhere. 也就是说,它只存储对某些地方的某些东西的引用。 Without boxing you cannot put an int
into such a collection. 没有拳击,你不能把int
放入这样的集合。 But with boxing, you can. 但是对于拳击,你可以。
Now, in the days of generics you don't really need this and can generally go merrily along without thinking about the issue. 现在,在仿制药的时代,你真的不需要这个,并且通常可以快乐地走,而不考虑问题。 But there are a few caveats to be aware of: 但有一些需要注意的注意事项:
This is correct: 这是对的:
double e = 2.718281828459045;
int ee = (int)e;
This is not: 这不是:
double e = 2.718281828459045;
object o = e; // box
int ee = (int)o; // runtime exception
Instead you must do this: 相反,你必须这样做:
double e = 2.718281828459045;
object o = e; // box
int ee = (int)(double)o;
First we have to explicitly unbox the double
( (double)o
) and then cast that to an int
. 首先,我们必须显式地取消double
( (double)o
),然后将其转换为int
。
What is the result of the following: 以下是什么结果:
double e = 2.718281828459045;
double d = e;
object o1 = d;
object o2 = e;
Console.WriteLine(d == e);
Console.WriteLine(o1 == o2);
Think about it for a second before going on to the next sentence. 在继续下一句话之前,请考虑一下。
If you said True
and False
great! 如果你说True
False
! Wait, what? 等等,什么? That's because ==
on reference types uses reference-equality which checks if the references are equal, not if the underlying values are equal. 那是因为引用类型上的==
使用引用相等性来检查引用是否相等,而不是基础值是否相等。 This is a dangerously easy mistake to make. 这是一个非常容易犯的错误。 Perhaps even more subtle 也许更微妙
double e = 2.718281828459045;
object o1 = e;
object o2 = e;
Console.WriteLine(o1 == o2);
will also print False
! 也将打印False
!
Better to say: 更好地说:
Console.WriteLine(o1.Equals(o2));
which will then, thankfully, print True
. 然后,谢天谢地,打印出True
。
One last subtlety: 最后一个微妙之处:
[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);
What is the output? 什么是输出? It depends! 这取决于! If Point
is a struct
then the output is 1
but if Point
is a class
then the output is 2
! 如果Point
是一个struct
那么输出是1
但如果Point
是一个class
那么输出是2
! A boxing conversion makes a copy of the value being boxed explaining the difference in behavior. 装箱转换使得装箱的值的副本解释了行为的差异。
#5楼
The last place I had to unbox something was when writing some code that retrieved some data from a database (I wasn't using LINQ to SQL , just plain old ADO.NET ): 我必须解开的最后一个地方是编写一些从数据库中检索一些数据的代码(我没有使用LINQ to SQL ,只是简单的旧ADO.NET ):
int myIntValue = (int)reader["MyIntValue"];
Basically, if you're working with older APIs before generics, you'll encounter boxing. 基本上,如果你在泛型之前使用旧的API,你会遇到拳击。 Other than that, it isn't that common. 除此之外,它并不常见。
#6楼
Boxing isn't really something that you use - it is something the runtime uses so that you can handle reference and value types in the same way when necessary. 拳击并不是你真正使用的东西 - 它是运行时使用的东西,因此你可以在必要时以相同的方式处理引用和值类型。 For example, if you used an ArrayList to hold a list of integers, the integers got boxed to fit in the object-type slots in the ArrayList. 例如,如果您使用ArrayList来保存整数列表,则整数将被装箱以适合ArrayList中的对象类型插槽。
Using generic collections now, this pretty much goes away. 现在使用通用集合,这几乎消失了。 If you create a List<int>
, there is no boxing done - the List<int>
can hold the integers directly. 如果创建List<int>
,则没有完成装箱 - List<int>
可以直接保存整数。