如何使用结构删除编译器错误:“使用未赋值的局部变量”
本文关键字:赋值 局部变量 结构 何使用 删除 编译器 错误 | 更新日期: 2023-09-27 18:35:08
C#编译器有点...老式。。。并且不会进行静态分析。所以它会像这样破坏看似正确的代码:
MyStruct s;
bool inited = false;
foreach( Something foo in ACollection )
{
if( foo.Test() )
continue;
if( inited )
s.DoSomething();
else
{
s = foo.GetMeAnS();
inited = true;
}
}
注意:不寻常的问题是"s"是一个结构体。如果它是一个类,我会简单地将其初始化为 null。这个结构没有有意义的"外行"状态,我不想支付启动我立即丢弃的东西的性能成本,只是为了满足一个弱编译器。
代码(应该)完全正确:在启动 s 之前无法访问 s。(我已经从实际代码中复制/粘贴,但为了简单起见,我编辑掉了长方法名称)。
Mono 中的 C# 编译器曾经允许这样做,但现在不这样做了。除了编译器之外,没有任何变化,它现在在未赋值变量上给出错误。
有没有一种代码方法可以告诉它闭嘴,管好自己的事?:)我不想摆弄更改编译器设置(如果可能),因为代码是由其他人/组织编译的 - 我更喜欢解决问题的代码方式。
有没有一种代码方法告诉它闭嘴并管好自己的事?
编译器的业务是实现 C# 规范。编写的代码不应根据 C# 规范进行编译。无需明确分配s
即可访问s.DoSomething()
调用,因此您的代码已损坏。这不是编译器的错。如果 Mono 编译器曾经允许它,这是一个显然现在已经修复的错误。
修复它的最简单方法是明确分配值,当然:
MyStruct s = new MyStruct(); // Value will never actually be used
在很多情况下,我们(作为人类)可以判断某些事情永远不会发生,但编译器不能。这是另一个示例:
public int Foo(int input)
{
if (input >= 0)
{
return input;
}
else if (input < 0)
{
return -input;
}
// This is still reachable...
}
我们知道每个int
输入都会进入其中一个if
主体,但编译器仍然会(正确)在上面的代码上给出编译错误,因为右大括号是可访问的,并且它是一个非 void 方法。
您声称"代码(应该)完全正确"是根据您的推理,而不是 C# 特定......编译器只关心后者。
需要注意的一件事:规范甚至不关心我们在某些情况下确实将inited
设置为 true 的事实。即使它总是有 false
的值,它仍然只是一个局部变量,而不是一个常量表达式。下面是一个简单的示例,演示了没有循环的情况:
static void Main()
{
int x;
bool condition = false;
if (condition)
{
Console.WriteLine(x);
}
}
这仍然会给出一个错误:"错误 CS0165:使用未赋值的局部变量 'x'"
从 C# 5 规范的第 8.7.1 节:
如果 if 语句可访问并且布尔表达式没有常量值
false
,则if
语句的第一个嵌入式语句是可访问的。
这里的表达式是 condition
,它是一个局部变量。局部变量在技术术语中不是常量表达式,即使它永远不会改变。如果将其设置为局部常量,它将编译:
static void Main()
{
int x;
const bool condition = false;
if (condition)
{
Console.WriteLine(x);
}
}
现在有一个关于无法访问if
语句正文的警告 - 但没有错误。
C# 规范的设计使得你永远不能使用未初始化的变量。这不是一个老式的概念。处理这个问题的老式方法(C++)是说,"这是未定义的行为,任何事情都可能发生"。
奇怪的是,这种态度导致了很多错误,这导致许多编译器自动启动(在调试模式下)变量到0xDEADBEEF这样的 gem 中。
至于为什么 C# 编译器不进行代码分析来查找变量在到达该代码时是否启动?
停止问题
问题可以像这样重写。
bool inited = false;
MyStruct? s;
while (true)
{
foreach( Something foo in ACollection )
foo.Test();
if( inited && false == s.HasValue )
return;
}
这只是略有更改的代码。但是你可以看到,我已经把你的问题转换成了停止问题,输入在哪里,每个Something
的状态和foo.Test()
的实现。
这在图灵机上被证明是不可判定的,而您的 CLR VM 肯定是
。简而言之,你在问,为什么微软在编写C#编译器时没有违反数学和计算机科学的定律。
或者你在问,Microsoft C# 编译器在放弃停止问题之前不应该努力尝试吗?我的回答是,他们应该努力吗?