Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/blackberry/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 运行时如何知道装箱值类型的确切类型?_C#_Boxing_Unboxing - Fatal编程技术网

C# 运行时如何知道装箱值类型的确切类型?

C# 运行时如何知道装箱值类型的确切类型?,c#,boxing,unboxing,C#,Boxing,Unboxing,我明白什么是拳击。值类型被装箱到对象/引用类型,然后作为对象存储在托管堆上。但我无法通过拆箱 取消装箱将对象/引用类型转换回值类型 int i = 123; // A value type object box = i; // Boxing int j = (int)box; // Unboxing 好的。但如果我尝试将一个值类型取消装箱到另一个值类型,例如,在上面的示例中,long,它抛出InvalidCastException long d = (lo

我明白什么是拳击。值类型被装箱到对象/引用类型,然后作为对象存储在托管堆上。但我无法通过拆箱

取消装箱将对象/引用类型转换回值类型

int i = 123;          // A value type
object box = i;       // Boxing
int j = (int)box;     // Unboxing
好的。但如果我尝试将一个值类型取消装箱到另一个值类型,例如,在上面的示例中,long,它抛出InvalidCastException

long d = (long)box;
这让我想到,运行时可能隐式地知道“box”对象中装箱的值类型的实际类型。如果我是对的,我想知道这种类型的信息存储在哪里

编辑:

因为
int
可以隐式转换为
long
。这让我很困惑

int i = 123;
long lng = i;

非常好,因为它不涉及装箱/拆箱。

每个引用对象都有一组与其关联的元数据。这包括给定对象的确切类型(这就是为什么可以使用类型安全)

因此,虽然
int
是按值计算的,但实际上缺少此信息(这并不重要),但一旦将其装箱,它将创建一个包含所有必要元数据的新对象。这也意味着,虽然
int
仅为4个字节,但装箱的
int
远不止于此-您现在有了一个引用(4-8个字节)、值本身(4)和元数据(包括特定的类型句柄)。这与C++不同,它允许您向任何类型的指针抛出任何指针(当您出错时,让您处理错误)。
同样,所有by-reference对象都具有此元数据。这是引用类型成本中相当重要的一部分,但也是确保类型安全性的方法。这也很好地说明了
int
ArrayList
是多么昂贵,以及为什么
int[]
List
的效率要高得多-即使忽略分配(更重要的是收集)堆对象和装箱和拆箱本身的成本,4字节的int也可能突然变成20字节,仅仅因为您正在存储对它的引用:)

这是因为装箱指令将值类型标记添加到结果对象中。当您从对象中取消装箱值时,此变量是已知类型(以及内存中的大小)。因此,必须将对象强制转换为原始值类型


在您的示例中,甚至不需要将其从int转换为long,因为这是一个隐式转换。

当一个值被装箱时,它会得到一个对象头。从System.Object派生的任何类型所具有的类型。该值位于该标题之后。标题包含两个字段,一个是“syncblk”,它的各种用途超出了问题的范围。第二个字段描述对象的类型

这就是你要问的问题。它在文献中有各种各样的名称,最常见的是“类型句柄”或“方法表指针”。后者是最准确的描述,它是指向CLR在加载类型时跟踪的信息的指针。许多框架特性都依赖于它。当然是Object.GetType()。代码中的任何强制转换以及is和as运算符使用它。这些石膏是安全的,所以你不能把狗变成猫,类型手柄提供了这种保证。装箱整数的方法表指针指向System.Int32的方法表

在泛型出现之前,装箱在.NET1.x中非常常见。所有常见的集合类型都存储对象而不是T。因此,将元素放入集合中需要(隐式)装箱,再次取出它需要使用强制转换显式取消装箱

为了使这一点有效,抖动很重要,不需要考虑转换需要的可能性。因为这需要更多的工作。因此,C语言包含了一条规则,即取消绑定到另一种类型是非法的。现在需要做的只是检查类型句柄,以确保它是预期的类型。抖动直接将方法表指针与您案例中System.Int32的指针进行比较。并且嵌入在对象中的值可以直接复制,而无需考虑任何转换问题。非常快,尽可能快,这一切都可以通过内联机器代码完成,而无需任何CLR调用

此规则特定于C#,VB.NET没有此规则。在这两种语言之间的典型权衡中,C#的重点是速度,VB.NET的重点是方便。当取消装箱不是问题时,转换为其他类型,所有简单值类型都实现IConvertible。您可以使用Convert helper类在代码中显式编写它:

        int i = 123;                    // A value type
        object box = i;                 // Boxing
        long j = Convert.ToInt64(box);  // Conversion + unboxing

这与VB.NET编译器自动生成的代码非常相似。

这是因为当您进行装箱而不是将值类型从堆栈移动到堆时,它会在堆中创建它的副本,并将它在堆栈中的引用存储在新的堆栈框中。 因此,原始堆栈对象(即值类型对象)及其数据类型信息保留在堆栈中,并保持其历史记录。 现在,在取消装箱时,它会将堆中的对象类型与堆栈中的原始数据类型进行比较,如果发现不匹配,就会给出错误。
因此,有必要使用在取消装箱时装箱的数据类型。

box.GetType()如何?运行时知道每个对象的类型。装箱的值没有什么不同。假设您有一个
,它是值类型,您将球放入
玻璃盒
。这会阻止你知道它是玻璃盒子里的球吗?你看得很清楚,不是吗?就像那样……这是一个有点令人困惑的问题。假设您创建了一个
Foo
实例(引用类型),然后将其分配给
对象
变量。如果您试图将其强制转换为
Bar
实例(也是引用类型),是否也会出现相同的异常?正如@JonathonReinhart所说,拳击/解围并不是真正的问题