为什么我们需要在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
为什么
有一个统一的类型系统和允许值类型具有从方式他们的底层数据的一个完全不同的表示形式的引用类型代表他们的底层数据(例如, int
仅仅是一个32比特铲斗这比完全不同的引用类型)。
想想看这样的。 你有一个变量o
类型的object
。 现在你有一个int
,你想要把它变成o
。 o
是一些参考的地方,和int
显然不是东西的参考地方(毕竟,这只是一个数字)。 所以,你做的是这样的:你犯了一个新的object
,可以存储int
,然后你指定一个引用该对象o
。 我们称这个过程为“拳”。
所以,如果你不关心有一个统一的类型系统(即引用类型和值类型有非常不同的表现,你不希望有一个共同的方式来“代表”两),那么你就不需要拳击。 如果你不关心有int
代表他们的潜在价值(即,而不是有int
作为参考类型太,只是存储它们的潜在价值的参考),那么你就不需要拳击。
我应该在哪里使用它。
例如,旧的集合类型ArrayList
只吃object
秒。 也就是说,只存储引用到生活的地方出头。 如果没有拳击时,你不能把一个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);
才去到下一个句子想想它的第二个。
如果你说的True
和False
太棒了! 等等,什么? 这是因为==
的引用类型使用引用相等,检查,如果引用是等价的,如果没有潜在的价值是相等的。 这是一个危险的容易犯的错误。 也许更微妙
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
是一个struct
则输出为1
,但如果Point
是一class
则输出为2
! 装箱转换,使被包装在解释行为的差异值的副本。
在.NET框架中,有两种类型 - 值类型和引用类型。 这是面向对象的语言比较常见的。
一个面向对象的语言的重要特征是处理在类型不可知方式实例的能力。 这被称为多态 。 既然我们要充分利用多态的,但我们的类型两个不同的物种,必须有某种方式把它们放在一起,所以我们可以处理一个或另一个相同的方式。
现在,早在古时(Microsoft.NET 1.0),无不是这种新奇的仿制药喧嚣。 你不能写了,可以服务于值类型和引用类型的一个参数的方法。 这是一个违反多态性。 所以拳击被采用为强迫值类型到一个对象的装置。
如果这是不可能的,该框架将与方法和类的唯一目的就是接受其他物种的种类而散落。 不仅如此,但由于值类型没有真正共享一个共同的祖先类型,你必须为每个值类型(位,字节,INT16,INT32,等等等)不同的方法重载。
拳击防止这种情况的发生。 这就是为什么英国庆祝节礼日。
理解这一点,最好的办法是看底层的编程语言C#基础上。
在最低级别的语言,如C,所有的变量去一个地方:堆栈。 每次宣布它会在栈中的变量。 他们只能是原始值,如布尔,字节,32位int,32位UINT,等该协议栈是既简单又快捷。 作为变量添加他们只是去一个在另一个上面,这样你就宣告第一坐镇说,0×00,下一个0x01,因此未来在RAM等,另外0×02,变量通常是在编译期,解决预时间,让你即使在程序运行前的地址是已知的。
在下一级别时,如C ++,第二存储器结构称为堆被引入。 你仍然大多居住在Stack,但特殊的整数称为指针可以被添加到堆栈,存储在内存地址为对象的第一个字节,而该对象住在堆。 堆是那种乱七八糟的,并保持比较昂贵的,因为不像堆栈变量他们不要堆放线性升后降的程序执行。 他们可以来去没有特定的顺序,并且可以增大和缩小。
与指针处理是很难的。 他们是内存泄漏,缓冲区溢出和沮丧的原因。 C#救援。
在更高的层次,C#,你不需要去想指针 - .net框架(用C ++编写)认为,这些对你和他们呈现给你的对象的引用,以及性能,让您存储简单值喜欢的bool,字节和整数作为值类型。 下面实例化的昂贵的,内存托管堆一类去,而值类型你在低C级有同样的协议栈去引擎盖,对象和东西 - 速度超快。
用于保持的存储器这2个根本不同的概念(以及用于存储策略)从编码器的角度简单之间的相互作用的缘故,值类型可以在任何时间被装箱。 拳击导致从堆栈复制的值,放在一个对象,并放置在堆 -更贵,但是,与世界参考流体相互作用。 至于其他的答案指出,当你例如说,这将发生:
bool b = false; // Cheap, on Stack
object o = b; // Legal, easy to code, but complex - Boxing!
bool b2 = (bool)o; // Unboxing!
拳击的优势,强大的例证是空的检查:
if (b == null) // Will not compile - bools can't be null
if (o == null) // Will compile and always return false
我们的目标o是技术上指向我们的布尔B,已被复制到堆的副本堆栈的地址。 我们可以检查O为NULL,因为布尔的被装箱放在那里。
一般来说,你应该避免拳击,除非你需要它,例如通过一个int /布尔/无论作为对象的参数。 有在.net中的一些基本结构仍然需要传递值类型为对象(,因此需要拳击),但在大多数情况下,你应该永远不需要箱。
需要拳击,你要避免历史的C#结构的非详尽列表:
事件系统原来有一个竞争条件幼稚的使用它,它不支持异步。 添加在拳击问题,它可能应该避免。 (你可以取代它例如与使用泛型的异步事件系统。)
旧的线程和定时器模式被迫盒上的参数,但通过异步/等候一项已被替换为更干净,更高效。
在.NET 1.1集合完全依赖于拳击,因为他们泛型面前。 这些仍然在踢System.Collections中左右。 在任何新的代码,你应该使用来自System.Collections.Generic的集合,这除了避免拳击还为您提供更强的类型安全 。
您应该避免声明或通过你的值类型为对象,除非你有对付迫使拳击上述历史遗留问题,要避免拳击它的性能命中后,当你知道这将是无论如何装箱。
按照下面的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,布尔等原因拳,而事实上,他们是值类型简单的别名。 也就是说,.Net有类型,如布尔,的Int32,String和C#别名他们BOOL,INT,字符串,没有任何功能上的差异。
拳击是不是真的,你使用的东西 - 这是后话了运行时使用,这样就可以用同样的方法在必要时处理的参考和值类型。 例如,如果你使用一个ArrayList来保存整数列表,整数被人挡住,以适应在ArrayList中的对象类型的插槽。
使用泛型集合现在,这几乎消失。 如果您创建一个List<int>
,有没有做过拳击-在List<int>
可以直接持有的整数。
装箱和拆箱是专门用于治疗价值型对象作为引用类型; 它们的实际值移动到托管堆,并通过引用访问其值。
如果没有装箱和拆箱你永远无法通过引用传递值类型; 这意味着你不能错过的值类型为对象的实例。
编写从数据库中检索到的一些数据(我没有使用一些代码时,最后的地方,我不得不拆箱东西是LINQ到SQL ,只是普通的老ADO.NET ):
int myIntValue = (int)reader["MyIntValue"];
基本上,如果你是与旧的API泛型工作之前,你会遇到拳。 除此之外,它不是常见的。
拳击是必需的,当我们有需要对象作为参数的函数,但是我们有需要传递不同的值类型,在这种情况下,我们需要把它传递给函数之前先转换值类型对象数据类型。
我不认为这是真的,试试这个来代替:
class Program
{
static void Main(string[] args)
{
int x = 4;
test(x);
}
static void test(object o)
{
Console.WriteLine(o.ToString());
}
}
运行得很好,我没有使用装箱/拆箱。 (除非编译器在幕后?)
在.NET中,对象的每个实例,或由其衍生的任何类型的,包括数据结构,其包含有关它的类型的信息。 在.net中的“真实”值类型不包含任何此类信息。 以允许值类型的数据,以由期望接收从对象派生类型的例程被操纵时,系统会自动定义为每个值输入一个对应的类型具有相同的成员和字段。 拳击创建此类类型的新实例,从复制值类型的实例字段。 开箱副本从类类型的实例的字段的值的类型的实例。 所有这些都从值类型创建的类类型的从具有讽刺意味的命名的类值类型(其中,尽管它的名字,实际上是一个引用类型)的。
当一个方法只需要引用类型作为参数(比如限制的通用方法是通过一类new
约束),您将无法为引用类型传递给它,并有框它。
这也是该采取任何方法真实object
作为参数-这将是引用类型。
在一般情况下,你通常会希望避免你的拳击值类型。
不过,也有罕见的occurances这个地方是非常有用的。 如果您需要针对1.1框架,例如,您将无法访问泛型集合。 任何使用.NET 1.1藏品需要治疗你的价值类型为一个System.Object,这会导致装箱/拆箱。
还有这是在.NET 2.0 +有用的情况。 你想利用的事实,即所有类型,包括值类型,可直接视为一个对象的优势任何时候,你可能需要使用装箱/拆箱。 这有时可能得心应手,因为它可以让您保存在集合中任何类型的(通过使用对象,而不是T IN泛型集合),但在一般情况下,最好是避免这种情况,因为你失去的类型安全。 其中拳击频繁发生,虽然一个情况,就是当你使用反思 - 许多反射调用的值类型工作时,将需要装箱/拆箱,因为该类型是事先不知道。