using System;
public class Tester
{
public static void Main()
{
const uint x=1u;
const int y=-1;
Console.WriteLine((x+y).GetType());
// Let's refactor and inline y... oops!
Console.WriteLine((x-1).GetType());
}
}
Run Code Online (Sandbox Code Playgroud)
想象一下上面的代码在以下情况中使用:
public long Foo(uint x)
{
const int y = -1;
var ptr = anIntPtr.ToInt64() + (x + y) * 4096;
return ptr;
}
Run Code Online (Sandbox Code Playgroud)
内联看起来非常安全y,但事实并非如此.语言本身的这种不一致是违反直觉的,而且非常危险.大多数程序员只是内联y,但实际上你最终会出现整数溢出错误.事实上,如果你编写如上所述的代码,你很容易让下一个人在同一条代码内联工作y,甚至没有考虑过两次.
我认为这是C#的一个非常适得其反的语言设计问题.
第一个问题,在C#规范中定义了这种行为,为什么这样设计?
第二个问题,1.GetType()/ (-1).GetType()给出System.Int32.为什么它的表现不同const int y=-1?
第三个问题,如果它被隐式转换为uint,那么我们如何明确告诉编译器它是一个有符号整数(1i不是一个有效的语法!)?
最后一个问题,这不是语言设计团队所期望的行为(Eric Lippert会加入吗?),可以吗?
此行为由C#标准的第6.1.9节描述,隐式常量表达式转换:
•如果constant-expression的值在目标类型的范围内,则int类型的常量表达式(第7.19节)可以转换为sbyte,byte,short,ushort,uint或ulong类型.
所以你有const uint x = 1u;和常量表达(x - 1).
根据说明书,其结果x - 1通常是int,但因为常数表达式(即0)的值在其范围内uint将被视为uint.
请注意,这里编译器将处理1为unsigned.
如果更改表达式,(x + -1)则将其视为已-1签名并将结果更改为int.(在这种情况下,-in -1是一个"一元运算符",它将结果的类型转换-1为int,所以编译器不能再将其转换为uint像普通的那样1).
规范的这一部分意味着如果我们要将常量表达式更改为x - 2那么结果将不再是a,uint而是将转换为int.但是,如果您进行了更改,则会收到编译错误,指出结果将溢出a uint.
这是因为C#规范的另一部分,在7.19节常量表达式中指出:
常量表达式的编译时评估使用与非常量表达式的运行时评估相同的规则,除了运行时评估会抛出异常的情况,编译时评估会导致编译时错误发生.
在这种情况下,如果进行checked计算会出现溢出,因此编译器会发生故障.
关于这个:
const uint x = 1u;
const int y = -1;
Console.WriteLine((x + y).GetType()); // Long
Run Code Online (Sandbox Code Playgroud)
这与此相同:
Console.WriteLine((1u + -1).GetType()); // Long
Run Code Online (Sandbox Code Playgroud)
这是因为-1是类型int而1u类型uint.
第7.3.6.2节二进制数字促销描述了这个:
•否则,如果任一操作数的类型为uint而另一个操作数的类型为sbyte,short或int,则两个操作数都将转换为long类型.
(我省略了与此特定表达无关的部分.)
附录:我只想指出常数和非常数值之间的一元减号(又称"否定")运算符的细微差别.
根据标准:
如果否定运算符的操作数是uint类型,则将其转换为long类型,并且结果的类型为long.
对变量来说也是如此:
var p = -1;
Console.WriteLine(p.GetType()); // int
var q = -1u;
Console.WriteLine(q.GetType()); // long
var r = 1u;
Console.WriteLine(r.GetType()); // uint
Run Code Online (Sandbox Code Playgroud)
虽然对于编译时常量,如果表达式涉及使用它,则将值1转换为为了将整个表达式保持为a ,实际上将其视为.uintuintuint-1int
我同意OP - 这是非常微妙的东西,导致各种惊喜.
第一个问题,这个行为在C#规范中定义了什么
你的第一个问题是负责任的问题,马修沃特森的答案得到了回答.
为什么这样设计?
所有设计过程都需要在各种竞争设计目标之间进行权衡.C#的设计目标包括对C++开发人员熟悉的各种元素,与使用非.NET友好约定(如无符号整数类型)的非托管库进行互操作的能力,编写能够找出您的意思的编译器的能力模棱两可的情况,但在看起来你做错了什么时仍会通知你,等等.
"值可以无缝地替换为评估这些值的符号"是语言设计的一个很好的原则.但它不是唯一的一个.由于其中一些目标在您的情况下是矛盾的,因此需要付出一些代价.(另外,正如我在下面提到的,你不是要取代价值!)
我同意你说的事实,x + -1并x - 1有不同的类型是怪异.你想要它们是什么类型的?
让我们假设你想要它们都很长.现在我们有以下问题:什么是类型x - x?如果它是uint,因为我们有两个不同的uint,那么我们有奇怪的x - x和x - 1不同的类型.如果它很长,那么我们就会感到奇怪的是,适合uint的两个uint的差异并不是一个因素.
让我们假设你想要他们两个都是uint.那应该x + anySignedInt是uint?为什么它应该是uint而不是int?当然如果我们有uint 2和int -3,那么2 + -3应该是int -1.
无论你做什么,你最终都会遇到奇怪的情况.那是因为无符号数量不符合通常的算术规则.语言设计团队在糟糕的情况下尽其所能.
这些决定是如何在17年前达成的具体细节在时间的迷雾中消失了.
第二个问题,1和-1的类型是System.Int32.那么为什么它与const int y = -1的行为不同?
我想你的问题是"为什么x + y和x - 1明明他们是等价的表达式分析方法不同?",但他们不一样的表情. x + y并且x + -1是相同的表达,并且它们被分析相同; 不适合uint的uint和int常量的总和会促使两者都变长.两个uint的区别是一个uint.
你的根本错误是你认为增加一个负数,减去一个正数是相同的.在无符号算术中,它们不是因为在无符号算术中没有"添加负数"这样的东西.没有负面的!
如果隐式转换为uint,那么我们如何明确地告诉编译器它是有符号整数(1i不是有效的语法!)?
我不明白这个问题.你告诉编译器带有强制转换的东西的类型,但我不认为这就是你所要求的.
最后一个问题,这不是语言设计团队所期望的行为(Eric Lippert会加入吗?),可以吗?
我不再代表语言设计团队发言,但我可以告诉你,如果我在以下时间问你这个问题我会说些什么:
语言设计团队强烈希望你不要使用uint,尤其是永远不要在同一个表达式中混合使用int和uint,因为这样做会让人感到困惑和怪异.仅使用uint与使用uint的非托管代码进行互操作.
您会注意到uint不在公共语言子集中,并且许多逻辑上永远不会消极的数量,例如字符串或数组的长度,在.NET中始终是整数.这是有原因的.使用整数或多头.
| 归档时间: |
|
| 查看次数: |
296 次 |
| 最近记录: |