为什么无法将包含装箱整数的对象转换为双精度数?

huangapple go评论79阅读模式
英文:

Why can't C# cast an object containing a boxed int to a double?

问题

在C#中,为什么示例1可以工作:

int myValue1 = 11;
double resultDirectlyFromInt = myValue1;

但示例2不可以:

int myValue2 = 22;
object myObject2 = myValue2;
double resultFromBoxedInt = (double)myObject2;

然而,示例3又可以工作:

double myValue3 = 33.3;
object myObject3 = myValue3;
double resultFromBoxedDouble = (double)myObject3;

有人能解释背后的原理吗?因为对我来说,工作的示例1和示例3看起来像是示例2应该能工作的证明。

英文:

In C#, why does Example 1 work:

int myValue1 = 11;
double resultDirectlyFromInt = myValue1;

But Example 2 does not:

int myValue2 = 22;
object myObject2 = myValue2;
double resultFromBoxedInt = (double)myObject2;

Yet again Example 3 does work:

double myValue3 = 33.3;
object myObject3 = myValue3;
double resultFromBoxedDouble = (double)myObject3;

Can someone explain the rationale behind that ? Because to me, working examples 1 and 3 look like proofs that example 2 should work.

答案1

得分: 5

因为对我来说,示例1和示例3看起来像是示例2应该有效的证明。

不,它们是非常不同的转换。

C#定义了从intdouble的转换(请参阅C# 7草案标准的第10.2.3节)。但是,拆箱转换的定义方式非常不同,在第10.3.7节中有详细说明:

对于non_nullable_value_type的拆箱操作首先检查对象实例是否是给定non_nullable_value_type的装箱值,然后将值从实例中复制出来。

...

如果源操作数是指向不兼容对象的引用,则会引发System.InvalidCastException

在你的示例2中,对象实例不是类型为double的装箱值 - 它是类型为int的装箱值,因此上述描述的检查失败。

碰巧的是,.NET CLR在拆箱方面比C#要宽容一些;你可以从装箱的int拆箱到uint或反之亦然,还可以从枚举拆箱到其基础类型或反之亦然。但它不允许需要表示更改的任何操作,比如intdouble

如果你想要对这些规则进行解释,可以这样想:为了执行任何执行实际工作的转换(而不仅仅是从一个地方复制位模式到另一个地方),编译器需要告诉CLR执行哪种转换。对于拆箱操作,实际类型只有在执行时才知道,因此编译器无法指示CLR要执行什么操作。CLR可以有更复杂的规则,以便可以根据执行时的类型执行转换 - 但这将涉及通常基于语言的领域。

英文:

> Because to me, working examples 1 and 3 look like proofs that example 2 should work.

No, they're very different conversions.

C# defines a conversion from int to double (see section 10.2.3 of the C# 7 draft standard). But unboxing conversions are defined very differently, in section 10.3.7:

> An unboxing operation to a non_nullable_value_type consists of first checking that the object instance is a boxed value of the given non_nullable_value_type, and then copying the value out of the instance.
>
> ...
>
> If the source operand is a reference to an incompatible object, a System.InvalidCastException is thrown.

In your example 2, the object instance is not a boxed value of type double - it's a boxed value of type int, so the check described above fails.

As it happens, the .NET CLR is a little more forgiving in terms of unboxing than C# requires; you can unbox from a boxed int to a uint or vice versa, and also from an enum to its underlying type or vice versa. But it doesn't permit anything that requires a representation change, like int to double.

If you want a justification for those rules, think of it this way: in order to perform any conversion that performs real work (as opposed to just copying a bit pattern from one place to another), the compiler needs to tell the CLR which conversion to perform. For an unboxing operation, the actual type is only known at execution time, so the compiler can't instruct the CLR what to do. The CLR could have more complex rules so that it could perform the conversion based on the execution-time type - but that's straying into territory that is usually language-based.

答案2

得分: 0

你的第一个示例实际上调用了名为“隐式转换操作符”的操作符,你可以在此链接中找到更多信息(https://learn.microsoft.com/en-us/dotnet/api/system.double?view=net-7.0#type-conversions-and-the-double-structure),这是在编译器级别实现的。之所以能工作的原因是编译器知道在编译时Int32整数可以完全表示为Double类型的变量。

在你的第二个示例中,它不起作用的原因是在取消装箱时,你必须首先将其强制转换回原始类型,以便运行时可以确保类型安全。参考其他答案
相反,你可以将示例2更改如下,以使其在运行时不会投诉,这有点像解释示例1在底层是如何通过转换为double工作的,但是显式地。

int myValue2 = 22;
object myObject2 = myValue2;
double resultFromBoxedInt = (double)(int)myObject2;
英文:

Your first example is actually calling the operator called implicit conversion operator, which you can find more info in this link, and this happen to be implemented in compiler level. The reason why it works is compiler knows that Int32 integers can be represent fully in a Double-type variable during compile time.

In your example 2, the reason why it does not work is it during unbox, you must cast it back to the original type first, so that the runtime can ensure the type safety. Reference to other answers as well.
Instead, you can change the example 2 as below so that it will run without runtime complaining, which is kind of explaining how example 1 works under-the-hood with the cast to double, but explicitly.

int myValue2 = 22;
object myObject2 = myValue2;
double resultFromBoxedInt = (double)(int)myObject2;

huangapple
  • 本文由 发表于 2023年7月27日 23:47:11
  • 转载请务必保留本文链接:https://go.coder-hub.com/76781452.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定