如何使用结构删除编译器错误:“使用未赋值的局部变量”

本文关键字:赋值 局部变量 结构 何使用 删除 编译器 错误 | 更新日期: 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# 编译器在放弃停止问题之前不应该努力尝试吗?我的回答是,他们应该努力吗?