【问题标题】:Non-nullable reference type: why is my object considered nullable by the compiler?不可为空的引用类型:为什么编译器认为我的对象可以为空?
【发布时间】:2020-06-19 15:15:59
【问题描述】:

我有一个项目启用了新的Nullable reference type feature

 <Nullable>enable</Nullable>

现在让我们考虑这段代码

public class Foo  {    }

var foo = new Foo();

编译器认为foo 变量可以为空(Foo?)。 这是为什么?我不明白。

现在启用 Nullable 引用类型功能后,返回的 Foo 对象不应为 null,因为它是不可为 null 的类型。

如果我希望它可以为空,我会指定它有一个 Foo?

那么为什么编译器说它是一个可为空的变量呢?

谢谢

编辑

这是我在这里描述的屏幕截图。当您将鼠标悬停在 foo 变量上时

【问题讨论】:

  • 无法重现,var foo = new Foo(); 不显示任何警告。 Foo类里面是什么?
  • Foo 类中没有任何内容,如我的代码示例所示,它是空的。没有警告,但在 VS 中,您将鼠标悬停在 foo 变量上,它说它是 Foo?
  • 这里提出了类似的问题:Why does Visual Studio Type a Newly Minted Array as Nullable? 它回答了你的问题吗?

标签: c# .net-core nullable-reference-types


【解决方案1】:

在最初的实现中,foo 会被推断为Foo

但是,人们抱怨这妨碍了以下事情:

string? GetThing() => ...

var result = "";
if (condition)
{
    result = GetThing();
}

如果result 被推断为string,则result = GetThing() 行会导致警告:GetThing() 返回string?,如果您尝试将string? 分配给@,则会出现警告987654330@.

解决方案是将result 推断为string?,但编译器知道它当前不为空(其“流状态”为“NotNull”)。

这意味着:

string? GetThing() => ...

var result = "";

// No warning, as the compiler knows that result isn't null
int l1 = result.Length; 

if (condition)
{
    result = GetThing();
}

// Warning: the compiler knows 'result' might have been re-assigned
int l2 = result.Length; 

有关工作中的流状态的其他示例,请参阅以下内容:

string? result = GetString();
if (result == null)
    throw new Exception();

// No warning: the compiler knows that result can't be null here: if it was,
// the exception above would have been thrown
int l1 = result.Length;
string? result = GetString();

// Warning: result might be null
int l1 = result.Length; 

// No warning: the compiler knows that result can't be null here: if it was,
// the line above would have thrown
int l2 = result.Length; 
string result = "hello";
if (result == null)
    Console.WriteLine("NULL!");

// Warning: because we checked for null above, the compiler assumes that we
// know something that it doesn't, and so result might be null.
int l1 = result.Length;

【讨论】:

  • 好的,谢谢您的回答。正如你所说的The solution was to infer result as a string?, but the compiler knows that it's currently not null (its "flow state" is "NotNull"),但我呢?我的意思是现在我非常困惑,因为每次我将鼠标悬停在一个变量上时,它都会告诉我它是一个可为空的,然后我必须检查这是否真的是一个可为空的......有什么方法可以在智能感知中改变这种行为?或者是不使用var关键字的唯一方法?
  • 我的建议是不要担心。无论如何,将局部变量显式标记为可为空的能力有点可疑:严格来说没有必要,因为编译器在那里拥有它需要的所有信息,然后知道它是否可能为空。如果您的变量是 Foo? 而不是 Foo,那么您应该没有理由关心。
  • 嗯,我认为有充分的理由关心它。因为如果它是一个可为空的变量,那么我必须在使用它之前检查这个变量是否为空。我有一个存储库,例如,它在 GetById 方法上返回一个可为空的对象,但在 Search 方法上返回一个不可为空的数组。如果没有结果与搜索参数匹配,则该数组可能为空,但它永远不能为空。但是现在编译器只是说返回的对象可能是可空的,这让我感到困惑。
  • 现在在我的代码中,我的反应就像Wait what?!! Is the object really nullable here? Ah no this is just the damn compiler lying to me...
  • 老实说,这简直是愚蠢的。如果var 关键字只是盲目地使每个引用类型都可以为空,那么您将失去显式可空类型的优势。似乎唯一的解决方案是显式写出类型并完全避免使用var 关键字,这很快就会变得乏味。我真的希望他们包含一个项目标志来禁用这种行为。
【解决方案2】:

查看the specification on nullable reference types。它指出var 推断引用类型的注释类型。

可为空的隐式类型局部变量标题下的部分内容如下:

var 推断引用类型的注释类型。例如,在 var s = ""; var 被推断为字符串?。

【讨论】:

  • 就实际(当前)实现而言,该规范似乎明显不正确。如果这是正确的,var s = ""; int i = s.Length; 应该会产生一个警告;它没有(也不应该)。它进一步指出,插值字符串永远不会是null,因此这意味着var s = $"" 会有所不同,并输入为string——没有明显的原因。不过,分析器中反映的可能是实现中的这种差异。
  • later part of that spec 似乎与引用的部分相矛盾。 - “用 var 声明的局部变量推断的类型由初始化表达式的 null 状态通知。”
  • @Damien_The_Unbeliever:我实际上错过了相关位,但它确实明确自相矛盾:“null 文字的空状态是“可能为空”。default 文字的空状态被转换为已知不是不可空值类型的类型是“可能为空”。任何其他文字的空状态是“非空”。因此,"" 的 null 状态为“not null”(当然应该如此),为 var s = "" 推断的类型是 string,尽管有示例。
  • 这是一个最近发生了变化的区域 IIRC。我相信当前的实现会将varstring? 一样对待,但是好像用户已经对其进行了空检查,所以编译器知道它不能为空(除非你分配其他东西给它)
  • @canton7:这将与当前实现一致,它允许将var foo = new Foo() 重新分配为null(当然Foo foo = new Foo() 不允许这样做)。不幸的是,似乎没有人能够将规范更新为至少不矛盾......但我认为“pull request plz”会满足这种效果。 :-P
猜你喜欢
  • 1970-01-01
  • 2010-10-10
  • 2022-12-03
  • 2020-10-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-06-26
  • 2021-11-03
相关资源
最近更新 更多