40

有人可以澄清一下 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# 在调试器和编译器中会引发转换/转换错误,但在运行时不会?

4

5 回答 5

37

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

您不能直接在 C#之间int[]进行转换,因为该语言不相信任何转换可用。但是,如果您通过结果取决于 CLI。来自 CLI 规范第 8.7 节(我希望 - 我引用了我不久前与 Eric Lippert 就该主题进行的电子邮件交流):uint[]object

有符号和无符号整数基元类型可以相互分配;例如, int8 := uint8 是有效的。为此,bool 应被认为与兼容,uint8反之亦然,这使得bool := uint8有效,反之亦然。对于大小相同的有符号和无符号整数原始类型的数组也是如此;例如,int32[] := uint32[]有效。

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

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

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

于 2009-02-27T07:05:00.540 回答
4

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

  • 数组继承自 System.Array。

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

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

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

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

于 2009-02-27T07:12:49.520 回答
1

建议:

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

关于 Q2、Q3:

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

来自 MSDN 上的这篇文章:

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

...

默认情况下,这些非常量表达式在运行时也不会检查溢出,并且它们不会引发溢出异常。前面的示例将 -2,147,483,639 显示为两个正整数的和。

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

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

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

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

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

关于 Q1:

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

来自 MSDN

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

于 2009-02-27T06:48:04.790 回答
0

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

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

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

Animal[] tigers = new Tiger[10];
tigers[3] = new Elephant(); // ArrayTypeMismatchException
于 2009-02-27T06:54:57.660 回答
0

好的,

我试图对此进行尝试。

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

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

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

于 2009-02-27T07:08:57.283 回答