为啥 C# 中的“int[] 是 uint[] == true”

Posted

技术标签:

【中文标题】为啥 C# 中的“int[] 是 uint[] == true”【英文标题】:Why does "int[] is uint[] == true" in C#为什么 C# 中的“int[] 是 uint[] == true” 【发布时间】:2010-10-10 06:33:51 【问题描述】:

请有人澄清 C# is 关键字。特别是这两个问题:

Q1) 第 5 行;为什么返回 true?

Q2) 第 7 行;为什么没有强制转换异常?

public void Test()

    object intArray = new int[]  -100, -200 ;            

    if (intArray is uint[]) //why does this return true?
    
        uint[] uintArray = (uint[])intArray; //why no class cast exception?

        for (int x = 0; x < uintArray.Length; x++)
        
            Console.Out.WriteLine(uintArray[x]);
        
    

MSDN 的描述没有说明情况。它声明is 将在满足其中任何一个条件时返回 true。 (http://msdn.microsoft.com/en-us/library/scekt9xw(VS.71).aspx>MDSN 文章)

表达式不为空。 表达式可以转换为类型。

我不相信您可以将 int[] 有效地转换为 uint[]。因为:

A) 此代码无法编译:

int[] signed = new int[]  -100 ;
uint[] unsigned = (uint[])signed; 

B) 在调试器中进行强制转换会出错:

(uint[])signed
"Cannot convert type 'int[]' to 'uint[]'"

果然,如果第 3 行是 int[] 而不是 object 那么它永远不会编译。这让我想到了与 Q2 相关的最后一个问题。

Q3) 为什么 C# 会在调试器和编译器中引发转换/转换错误,但在运行时不会?

【问题讨论】:

当你需要 Eric Lippert 时他在哪里? 错了,这是 Stack Overflow,你的意思是“当你需要 Jon Skeet 时他在哪里?” ;) 检查我的答案,我更新了一个视频链接,我认为该视频解释了潜在问题。看看吧! 埃里克·利珀特在这里:why-does-my-c-sharp-array-lose-type-sign-information-when-cast-to-object 【参考方案1】:

C# 和 CLR 的转换规则有些不同。

您不能直接在 C# 中在 int[]uint[] 之间进行转换,因为 语言 不相信任何转换可用。但是,如果您通过 object 访问,则结果取决于 CLI。来自 CLI 规范第 8.7 节(我希望 - 我刚才引用了 email exchange I had on this topic with Eric Lippert):

有符号和无符号整数原语 类型可以相互分配; 例如, int8 := uint8 是有效的。为了这 目的,应考虑 bool 与uint8 兼容,反之亦然, 这使得bool := uint8 有效,并且 反之亦然。这也适用于 有符号和无符号整数数组 相同大小的原始类型; 例如,int32[] := uint32[] 是有效的。

(我没有检查过,但我认为这种引用类型转换是有效的,这也是 is 返回 true 的原因。)

不幸的是,语言与底层执行引擎之间存在脱节,但我怀疑从长远来看,这几乎是不可避免的。还有一些类似的案例,但好消息是它们似乎很少造成重大伤害。

编辑:由于 Marc 删除了他的答案,我已链接到 Eric 的完整邮件,该邮件已发布到 C# 新闻组。

【讨论】:

听起来像是“你可以在 MSIL 中做什么而在 c# 中不能”问题的另一个候选人。直接转换,无需使用对象/数组变量来安抚 c# 编译器 @ShuggyCoUk:好电话 - 将更新我的答案,链接到这个问题。 对我来说听起来不太安全。【参考方案2】:

现在这很有趣。我在 ECMA-335 标准中发现了这一点。 4.3 演员表。请注意:

数组继承自 System.Array。

如果 Foo 可以转换为 Bar,则 Foo[] 可以转换为 Bar[]。

出于上述注释 2 的目的,枚举被视为其基础类型:因此,如果 E1 和 E2 共享基础类型,则 E1[] 可以强制转换为 E2[]。

您可以将 int 转换为 uint,但它的行为方式非常奇怪。 Visual Studio 无法识别任何这些,即使是手表,当附加调试器时只会显示一个问号“?”。

你可能想看看this,快进大约 10 分钟,然后听安德斯解释协变数组的实现。我认为这是根本的根本问题。

【讨论】:

【参考方案3】:

建议:

将 intArray 声明为“int [] intArray”而不是“object intArray”将允许编译器拾取无效的 C# 转换。除非您绝对必须使用对象,否则我会采用这种方法。

关于第二季度,第三季度:

在运行时,您是否尝试过将演员表包装在 checked 块中?

来自 MSDN 上的这篇文章:

默认情况下,一个表达式 仅包含常量值会导致 如果表达式编译器错误 产生一个超出范围的值 目标类型的范围。如果 表达式包含一个或多个 非常量值,编译器会 没有检测到溢出。

...

默认情况下,这些非常量 不检查表达式 在运行时溢出,它们 不要引发溢出异常。这 上一个示例显示 -2,147,483,639 为两个正整数之和。

溢出检查可以通过 编译器选项,环境 配置,或使用检查 关键字。

正如它所说,您可以通过编译器设置或环境配置在全局范围内强制执行溢出检查。

在您的情况下,这可能是可取的,因为它会导致抛出运行时错误,这将确保可能的无效无符号数到有符号数溢出不会静默发生。

[更新] 在测试这段代码后,我发现使用 object 类型的声明而不是 int [] 似乎绕过了标准的 C# 转换语法,无论是否启用了检查。

正如 JS 所说,当你使用对象时,你会受到 CLI 规则的约束,而这些显然允许这种情况发生。

关于 Q1:

这与上述有关。简而言之,因为涉及的演员阵容不会引发异常(基于当前的溢出设置)。这是否是一个好主意是另一个问题。

From MSDN:

如果提供的表达式不为空,则“is”表达式的计算结果为 true,并且 提供的对象可以转换为提供的类型而不会导致异常 扔了。

【讨论】:

但它并没有回答他的第一个问题,“为什么 (intArray is uint[])” 不,令人惊讶的是,检查并没有发现这一点。调试器完全被内容弄糊涂了(只显示 ? 用于检查器中的元素),并且取出值得到包装的值。 我的测试中的行为相同。当你需要乔恩·斯基特时,他在哪里? 在那儿。 (可能很快就会出现:)【参考方案4】:

我猜测与 .NET 1 的向后兼容性:我对细节仍然有些模糊,但我相信所有数组的 CLR 类型都只是 System.Array,具有额外的 Type 属性来查找元素类型。 'is' 可能只是在 CLR v1 中没有考虑到这一点,现在必须保持这一点。

它在 (uint[])(new int[]) 的情况下不起作用可能是因为 C# 编译器(而不是 CLR 运行时)能够进行更严格的类型检查。

此外,数组通常只是类型不安全的:

Animal[] tigers = new Tiger[10];
tigers[3] = new Elephant(); // ArrayTypeMismatchException

【讨论】:

【参考方案5】:

好的,

我试图对此进行尝试。

首先,文档说,“检查一个对象是否与给定类型兼容。”,它还说如果左边的类型是“可转换的”(你可以毫无例外地转换)到对,并且表达式的计算结果为非 null,“is”关键字的计算结果为 true

请向 Jon Skeet 寻求其他答案。他说得比我更有说服力。他是对的,如果转换可用,它将接受您的语法,您可以随后编写自己的语法,但在这种情况下似乎有点矫枉过正。

参考:http://msdn.microsoft.com/en-us/library/scekt9xw(VS.80).aspx

【讨论】:

以上是关于为啥 C# 中的“int[] 是 uint[] == true”的主要内容,如果未能解决你的问题,请参考以下文章

为啥 C# 中的诚实函数示例仍然不诚实?

为啥 C# 中的 1 && 2 为假?

仅在 C# 中的 Date 类型 - 为啥没有 Date 类型?

为啥我需要覆盖 C# 中的 .Equals 和 GetHashCode [重复]

为啥 C# 中的基类允许实现接口契约而不继承它?

C# 中的 XML - hasAttribute,getAttribute 不存在吗?为啥?