【问题标题】:Null propagation operator, out parameters and false compiler errors?空传播运算符,输出参数和错误的编译器错误?
【发布时间】:2025-12-05 10:35:02
【问题描述】:

假设我有一个类,其属性类型为Dictionary<string,string>,它可能为空。

这可以编译,但对 TryGetValue() 的调用可能会在运行时引发 NullRef 异常:

MyClass c = ...;
string val;
if(c.PossiblyNullDictionary.TryGetValue("someKey", out val)) {
    Console.WriteLine(val);
}

所以我添加了一个空传播运算符来防止空值,但这不会编译:

MyClass c = ...;
string val;
if( c.PossiblyNullDictionary ?. TryGetValue("someKey", out val) ?? false ) {

    Console.WriteLine(val); // use of unassigned local variable

}

是否存在valif 块内未初始化的实际用例,或者编译器不能简单地推断出这一点(以及为什么)?

更新:最干净的 (?) 解决方法^H^H^H^H^H 解决这个问题是:

MyClass c = ...;
string val = null; //POW! initialized.
if( c.PossiblyNullDictionary ?. TryGetValue("someKey", out val) ?? false ) {

    Console.WriteLine(val); // no more compiler error

}

【问题讨论】:

  • 编译器 prolly 不关心/不知道 TryGetValue 结果的语义。它只关心调用它会初始化 val。如果 TryGetValue 根本没有被调用,你就不能认为它已正确初始化。
  • @user6144226 只要 TryGetValue 运行,它返回什么都没关系。 C# 规范强制任何采用 out 参数的方法在返回之前对其进行初始化 - 否则该方法无法编译。
  • @CristiDiaconescu 如果你使用 ?。 TryGetValue 不会被调用。
  • @user6144226 当然可以,但是编译器传统上非常擅长计算常量值表达式。例如:if( true || c.Dict.TryGetValue(key, out val) ) // compiler error, uninitialized varif( false || c.Dict.TryGetValue(key, out val) ) // works
  • @Chris 关键是,OP 在表达式中有编译时间常数false,它仍然无法弄清楚。这不是Rand() %2 == 0 或任何疯狂的东西。这是编译时间常数。

标签: c# null-propagation-operator


【解决方案1】:

您似乎遇到了编译器对 ?.?? 理解的限制,这并不奇怪,因为它们并没有真正完全融入语言中。

如果您在没有较新的运算符的情况下使您的测试显式,编译器会同意您的意见:

MyClass c = new MyClass();
string val;
if (c.PossiblyNullDictionary != null && c.PossiblyNullDictionary.TryGetValue("someKey", out val)) {
    Console.WriteLine(val); // now okay
}

【讨论】:

  • 您可以建议对编译器进行改进,假设它不太复杂,总有一天它可能会完成。他们正在讨论为?.?? 添加Expression 支持,所以这也可以添加到...
  • 我认为 OP 知道这种方法,但试图避免必须有一个大于必要的 if 条件
  • @NetMage 你能解释一下你所说的“[未] 完全融入语言”的功能是什么意思吗?它们不是 C# 规范的一部分吗?
  • 一个(经常提到的)例子是Expression 不支持它们。显然另一个例子是这个,编译器不理解?./??,它理解&&||的方式。即使 SQL 具有 COALESCE 运算符,LINQ to SQL/Entities 也无法转换 ??。等等。
  • 不,?? 不是赋值运算符。 ??= 是一个赋值运算符,但尚未实现。 && 测试有效(我不会称之为分离)这一事实似乎表明三元运算符也没有完全处理。
【解决方案2】:

通过将 val 初始化为一个 erhm 值(例如,String.Empty),编译器能够理解空运算符的意图并按预期运行(通过 LINQPad,natch):

void Main()
{
    MyClass c = new MyClass();
    string val = string.Empty;
    if (c.PossiblyNullDictionary?.TryGetValue("someKey", out val) ?? false)
    {

        Console.WriteLine(val);

    }
}
public class MyClass {
    public Dictionary<string, string> PossiblyNullDictionary;
}
// Define other methods and classes here

Ed:'grok the intent' 我的意思是,如果编译器允许执行离开当前范围且 val 未初始化,则编译器无法对程序的特性做出重要保证。当它评估空运算符时,方法调用。

您要求的用例是这样的: 假设我们有 bool SomeMethod(string s, out v) 而不是 TryGetValue。假设当被调用时,SomeMethod 很调皮,只是有一个 return true; 的主体。编译器将方法调用主体视为不透明的(因为它可能并不总是在编译器可用/可见的程序集中),因此它得出结论,无法证明 val 曾经被初始化。

编辑: 针对一些 cmets,我想更新我的回答,指出这种行为并非特定于 ???. C# 语言功能;您只需使用三元表达式即可重现相同的效果:

c.PossiblyNullDictionary == null ? 
    false : 
    c.PossiblyNullDictionary.TryGetValue("someKey", out val) 
 //error: use of possibly uninitialized local variable

【讨论】:

  • 是的,这就是我通常最终会做的事情,请参阅 Q 结尾处的更新。我只是想知道我是否遗漏了什么并且编译器确实是正确的(通常是关于这种错误!)
  • @CristiDiaconescu:我认为编译器的正确之处在于它不知道它已被初始化。如果您执行bool val = true; string test; if (val){test = "hoorah!";} Console.WriteLine(test);,那么编译器会告诉您test 未初始化。我认为问题在于编译器认为 if 的结果可能是真或假。仅仅因为 我们 知道它要么是假的要么会运行 TryGetValue 并不意味着编译器看起来那么远。我认为这是因为这是一个编译器拒绝启动的兔子洞。
  • @chris 说的是我自己开始写的,虽然不太连贯
  • 我要补充的是编译器不知道TryGetValue 做了什么,只是它返回一个布尔值并传递了一种引用。它不知道函数将如何改变val 参考
  • @JoshE:不正确。编译器知道当函数调用返回时所有的 out 变量都会被初始化。这是 out 参数的要求(在从方法返回之前未分配给它的编译器错误)。
【解决方案3】:

这是因为如果 c.PossiblyNullDictionary 为 null,则不会执行 TryGetValue,并且该表达式不会返回 true 或 false。

c.PossiblyNullDictionary ?。 TryGetValue("someKey", out val) 返回 Nullable,你可以用这样的代码替换你的代码并编译:

        string val;
        var result = c.PossiblyNullDictionary?.TryGetValue("key", out val);
        if (result.HasValue && result.Value)
        {

        }

【讨论】:

  • ...因此最后是?? false
  • 您似乎对了一半,只是问题是因为TryGetValue 没有被执行,所以val 永远不会被初始化。
  • ?. null 传播将处理 PossiblyNullDictionary 为 null 并且不执行 TryGetValue - 从而导致 null 评估。空合并运算符 ?? 将处理该空,在这种情况下恢复为 false
  • 看看 Geoff 对你的问题的评论,他打败了我。
  • @olegk 并且您更新的答案仍然有相同的错误...使用未分配的局部变量val