为什么我们需要在 C# 中装箱和拆箱?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2111857/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Why do we need boxing and unboxing in C#?
提问by Vaibhav Jain
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
采纳答案by jason
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 (e.g., an int
is just a bucket of thirty-two bits which is completely different than a reference type).
拥有统一的类型系统并允许值类型对其底层数据的表示与引用类型表示其底层数据的方式完全不同(例如,anint
只是一个与引用完全不同的 32 位桶类型)。
Think of it like this. You have a variable o
of type object
. And now you have an int
and you want to put it into 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). 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
. We call this process "boxing."
像这样想。您有一个o
类型为 的变量object
。现在你有一个int
并且你想把它放入o
. o
是对某处某物的引用,而 theint
显然不是对某某某物的引用(毕竟,它只是一个数字)。因此,您要做的是:创建一个object
可以存储 的新int
对象,然后将该对象的引用分配给o
。我们称这个过程为“拳击”。
So, if you don't care about having a unified type system (i.e., 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 (i.e., 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. That is, it only stores references to somethings that live somewhere. Without boxing you cannot put an int
into such a collection. But with boxing, you can.
例如,旧的集合类型ArrayList
只吃object
s。也就是说,它只存储对某处某物的引用。没有拳击,你就不能把一个int
放进这样的集合中。但是用拳击,你可以。
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! 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
如果你说True
和False
伟大的!等等,什么?那是因为==
在引用类型上使用引用相等来检查引用是否相等,而不是基础值是否相等。这是一个很容易犯的危险错误。或许更微妙
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
! A boxing conversion makes a copy of the value being boxed explaining the difference in behavior.
输出是什么?这取决于!如果Point
是astruct
那么输出是1
但是如果Point
是aclass
那么输出是2
!装箱转换会复制被装箱的值,解释行为的差异。
回答by STW
Boxing and Unboxing are specifically used to treat value-type objects as reference-type; 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 的实例传递。
回答by BFree
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. Other than that, it isn't that common.
基本上,如果您在泛型之前使用较旧的 API,您会遇到拳击。除此之外,它并不常见。
回答by Ray
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>
可以直接保存整数。
回答by Oded
When a method only takes a reference type as a parameter (say a generic method constrained to be a class via the new
constraint), you will not be able to pass a reference type to it and have to box it.
当一个方法只接受一个引用类型作为参数时(比如一个通过约束被约束为类的泛型方法new
),你将无法将引用类型传递给它,而必须将它装箱。
This is also true for any methods that take object
as a parameter - this will haveto be a reference type.
对于任何object
作为参数的方法也是如此——这必须是一个引用类型。
回答by Oded
In the .NET framework, there are two species of types--value types and reference types. This is relatively common in OO languages.
在.NET 框架中,有两种类型——值类型和引用类型。这在 OO 语言中比较常见。
One of the important features of object oriented languages is the ability to handle instances in a type-agnostic manner. This is referred to as polymorphism. Since we want to take advantage of polymorphism, but we have two different species of types, there has to be some way to bring them together so we can handle one or the other the same way.
面向对象语言的重要特性之一是能够以与类型无关的方式处理实例。这称为多态性。由于我们想利用多态性,但我们有两种不同的类型,因此必须有某种方法将它们组合在一起,以便我们可以以相同的方式处理其中一种。
Now, back in the olden days (1.0 of Microsoft.NET), there weren't this newfangled generics hullabaloo. You couldn't write a method that had a single argument that could service a value type and a reference type. That's a violation of polymorphism. So boxing was adopted as a means to coerce a value type into an object.
现在,回到过去(Microsoft.NET 的 1.0),没有这种新奇的泛型喧嚣。您无法编写一个具有单个参数的方法,该方法可以为值类型和引用类型提供服务。这违反了多态性。因此采用装箱作为将值类型强制转换为对象的一种手段。
If this wasn't possible, the framework would be littered with methods and classes whose only purpose was to accept the other species of type. Not only that, but since value types don't truly share a common type ancestor, you'd have to have a different method overload for each value type (bit, byte, int16, int32, etc etc etc).
如果这是不可能的,那么该框架将充斥着方法和类,其唯一目的是接受其他类型的类型。不仅如此,由于值类型并没有真正共享一个共同的类型祖先,因此您必须为每个值类型(位、字节、int16、int32 等)使用不同的方法重载。
Boxing prevented this from happening. And that's why the British celebrate Boxing Day.
拳击阻止了这种情况的发生。 这就是英国人庆祝节礼日的原因。
回答by supercat
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).
在 .net 中,Object 的每个实例或从其派生的任何类型都包含一个数据结构,其中包含有关其类型的信息。.net 中的“真实”值类型不包含任何此类信息。为了允许期望接收从对象派生的类型的例程操作值类型中的数据,系统自动为每个值类型定义具有相同成员和字段的对应类类型。装箱创建此类类型的新实例,从值类型实例复制字段。拆箱将字段从类类型的实例复制到值类型的实例。所有从值类型创建的类类型都是从具有讽刺意味的命名类 ValueType 派生的(尽管它的名字,它实际上是一个引用类型)。
回答by Manoj
Boxing is required, when we have a function that needs object as a parameter, but we have different value types that need to be passed, in that case we need to first convert value types to object data types before passing it to the function.
装箱是必需的,当我们有一个需要对象作为参数的函数,但我们有不同的值类型需要传递时,在这种情况下,我们需要先将值类型转换为对象数据类型,然后再将其传递给函数。
I don't think that is true, try this instead:
我不认为这是真的,试试这个:
class Program
{
static void Main(string[] args)
{
int x = 4;
test(x);
}
static void test(object o)
{
Console.WriteLine(o.ToString());
}
}
That runs just fine, I didn't use boxing/unboxing. (Unless the compiler does that behind the scenes?)
运行得很好,我没有使用装箱/拆箱。(除非编译器在幕后这样做?)
回答by Hunain
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. Any use of the collections in .NET 1.1 would require treating your value type as a System.Object, which causes boxing/unboxing.
但是,在极少数情况下这是有用的。例如,如果您需要针对 1.1 框架,您将无法访问泛型集合。.NET 1.1 中集合的任何使用都需要将您的值类型视为 System.Object,这会导致装箱/拆箱。
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.
这在 .NET 2.0+ 中仍然有用。任何时候您想利用所有类型(包括值类型)都可以直接视为对象这一事实,您可能需要使用装箱/拆箱。这有时很方便,因为它允许您在集合中保存任何类型(通过在泛型集合中使用 object 而不是 T ),但一般来说,最好避免这种情况,因为您会失去类型安全性。但是,经常发生装箱的一种情况是当您使用反射时 - 反射中的许多调用在使用值类型时都需要装箱/拆箱,因为类型是事先未知的。
回答by Chris Moschini
The best way to understand this is to look at lower-level programming languages C# builds on.
理解这一点的最好方法是查看 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.
在像 C 这样的最低级语言中,所有变量都集中在一个地方:堆栈。每次声明一个变量时,它都会进入堆栈。它们只能是原始值,如 bool、字节、32 位 int、32 位 uint 等。堆栈既简单又快速。当添加变量时,它们只是一个在另一个之上,所以你声明的第一个位于 0x00,下一个在 0x01,下一个在 RAM 中的 0x02,等等。此外,变量通常在编译时预先寻址 -时间,所以他们的地址在你运行程序之前就已经知道了。
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 Pointerscan 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++,引入了称为堆的第二个内存结构。您仍然主要生活在堆栈中,但是可以将称为指针的特殊整数添加到堆栈中,用于存储对象第一个字节的内存地址,并且该对象位于堆中。堆有点乱,维护起来有点昂贵,因为与堆栈变量不同,它们不会在程序执行时线性堆积然后堆积。它们可以没有特定的顺序来来去去,也可以增长和缩小。
Dealing with pointers is hard. They're the cause of memory leaks, buffer overruns, and frustration. C# to the rescue.
处理指针很困难。它们是内存泄漏、缓冲区溢出和沮丧的原因。C# 来救援。
At a higher level, C#, you don't need to think about pointers - the .Net framework (written in C++) thinks about these for you and presents them to you as References to Objects, and for performance, lets you store simpler values like bools, bytes and ints as Value Types. Underneath the hood, Objects and stuff that instantiates a Class go on the expensive, Memory-Managed Heap, while Value Types go in that same Stack you had in low-level C - super-fast.
在更高级别,C#,您不需要考虑指针 - .Net 框架(用 C++ 编写)会为您考虑这些并将它们作为对对象的引用呈现给您,并且为了性能,您可以存储更简单的值像 bools、bytes 和 ints 作为值类型。在幕后,对象和实例化类的东西在昂贵的内存管理堆中,而值类型则在与低级 C 相同的堆栈中 - 超快。
For the sake of keeping the interaction between these 2 fundamentally different concepts of memory (and strategies for storage) simple from a coder's perspective, Value Types can be Boxed at any time. Boxing causes the value to be copied from the Stack, put in an Object, and placed on the Heap- more expensive, but, fluid interaction with the Reference world. As other answers point out, this will occur when you for example say:
为了从编码人员的角度保持这两个根本不同的内存概念(和存储策略)之间的交互简单,值类型可以随时装箱。装箱会导致值从堆栈中复制,放入一个对象中,然后放置在堆上- 更昂贵,但与引用世界的流畅交互。正如其他答案所指出的那样,例如,当您说:
bool b = false; // Cheap, on Stack
object o = b; // Legal, easy to code, but complex - Boxing!
bool b2 = (bool)o; // Unboxing!
A strong illustration of the advantage of Boxing is a check for null:
Boxing 优势的一个有力例证是检查 null:
if (b == null) // Will not compile - bools can't be null
if (o == null) // Will compile and always return false
Our object o is technically an address in the Stack that points to a copy of our bool b, which has been copied to the Heap. We can check o for null because the bool's been Boxed and put there.
我们的对象 o 从技术上讲是堆栈中的一个地址,它指向我们的 bool b 的副本,该副本已被复制到堆中。我们可以检查 o 是否为 null,因为 bool 已被装箱并放在那里。
In general you should avoid Boxing unless you need it, for example to pass an int/bool/whatever as an object to an argument. There are some basic structures in .Net that still demand passing Value Types as object (and so require Boxing), but for the most part you should never need to Box.
一般来说,除非需要,否则应该避免拳击,例如将 int/bool/whatever 作为对象传递给参数。.Net 中有一些基本结构仍然需要将值类型作为对象传递(因此需要装箱),但在大多数情况下,您永远不需要装箱。
A non-exhaustive list of historical C# structures that require Boxing, that you should avoid:
需要装箱的历史 C# 结构的非详尽列表,您应该避免:
The Event system turns out to have a Race Conditionin 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.
事实证明,事件系统在幼稚的使用中存在竞争条件,并且它不支持异步。添加拳击问题,它可能应该避免。(例如,您可以将其替换为使用泛型的异步事件系统。)
旧的 Threading 和 Timer 模型在其参数上强制使用 Box,但已被 async/await 取代,后者更简洁、更高效。
.Net 1.1 Collections 完全依赖于 Boxing,因为它们出现在泛型之前。这些仍然在 System.Collections 中出现。在任何新代码中,您都应该使用 System.Collections.Generic 中的集合,它除了避免装箱外,还为您提供更强的类型安全性。
You should avoid declaring or passing your Value Types as objects, unless you have to deal with the above historical problems that force Boxing, and you want to avoid the performance hit of Boxing it later when you know it's going to be Boxed anyway.
您应该避免将您的值类型声明或传递为对象,除非您必须处理上述强制使用 Boxing 的历史问题,并且您想避免稍后在您知道它无论如何都会被 Boxed 时对其进行 Boxing 的性能影响。
Per Mikael's suggestion below:
根据以下 Mikael 的建议:
Do This
做这个
using System.Collections.Generic;
var employeeCount = 5;
var list = new List<int>(10);
Not This
不是这个
using System.Collections;
Int32 employeeCount = 5;
var list = new ArrayList(10);
Update
更新
This answer originally suggested Int32, Bool etc cause boxing, when in fact they are simple aliases for Value Types. That is, .Net has types like Bool, Int32, String, and C# aliases them to bool, int, string, without any functional difference.
这个答案最初建议 Int32、Bool 等导致装箱,而实际上它们是值类型的简单别名。也就是说,.Net 具有 Bool、Int32、String 和 C# 等类型,将它们别名为 bool、int、string,没有任何功能差异。