C# Null传播运算符、out参数和假编译器错误?

C# Null传播运算符、out参数和假编译器错误?,c#,null-propagation-operator,C#,Null Propagation Operator,假设我有一个类,它的属性类型为Dictionary,可能为null 这会编译,但对TryGetValue()的调用可能会在运行时引发NullRef异常: MyClass c = ...; string val; if(c.PossiblyNullDictionary.TryGetValue("someKey", out val)) { Console.WriteLine(val); } 因此,我添加了一个null传播操作符来防止null,但这并不编译: MyClass c = ...;

假设我有一个类,它的属性类型为
Dictionary
,可能为null

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

MyClass c = ...;
string val;
if(c.PossiblyNullDictionary.TryGetValue("someKey", out val)) {
    Console.WriteLine(val);
}
因此,我添加了一个null传播操作符来防止null,但这并不编译:

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

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

}
是否存在一个实际的用例,其中
val
将在
if
块中被取消初始化,或者编译器不能简单地推断出这一点(以及原因)

更新:解决^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

}

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

c、 可能是字典?。TryGetValue(“someKey”,out val)返回可为null的值,您可以将代码替换为以下内容并编译:

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

        }

似乎编译器对
?。
的理解受到了限制,这并不令人惊讶,因为它们并没有完全融入到语言中

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

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

通过将
val
初始化为erhm、value(例如
String.Empty
),编译器能够找到空运算符的意图,并按照预期的方式运行(通过LINQPad、natch):



编译器通常不关心/不知道TryGetValue结果的语义。它只关心调用它将初始化val。如果根本没有调用TryGetValue,则不能假定它已正确初始化。@user6144226只要TryGetValue运行,它返回什么并不重要。C#规范强制任何取下
参数的方法在返回之前对其进行初始化,否则该方法无法编译。@CristiDiaconescu如果使用,这正是我的观点?。TryGetValue不会被调用。@user6144226当然可以,但编译器传统上非常擅长计算常量值表达式。例如:
if(true | c.Dict.TryGetValue(key,out val))//编译器错误,未初始化的var
但是
if(false | c.Dict.TryGetValue(key,out val))//works
@Chris问题是,OP在表达式中有编译时间常数
false
,它仍然无法计算出来。它不是
Rand()%2==0
或任何疯狂的东西。它是编译时常数…因此是
??false
结尾。您似乎有一半是对的,但问题是因为
TryGetValue
没有执行,所以
val
将永远不会被初始化。
?。
null传播将处理
可能的NullDictionary
为null而不执行
TryGetValue
-导致改为
null
求值。空合并运算符
将处理该空值,在本例中恢复为
false
。请看Geoff对您的问题的评论,他比我快。@olegk和您更新的答案仍然有相同的错误。。。使用未分配的局部变量
val
您可以建议对编译器进行改进,前提是它不太复杂,有一天可能会完成。他们正在讨论添加
表达式
的支持。
??
因此,这也可以添加进去……我认为OP知道这种方法,但正在尝试避免在
的情况下使用大于必要的
condition@NetMage你能解释一下你所说的“不”是什么意思吗完全融入语言“?它们不是C规范的一部分吗?一个(通常提到的)例子是
表达式不支持它们。显然,另一个例子是,编译器不理解
?。
/
??
它理解
&&
|
的方式。LINQ到SQL/实体无法转换
,即使SQL有
合并操作符。等等。不,
??
不是赋值运算符
??=
是一个赋值运算符,但尚未实现。事实上,
&
测试有效,我不会称之为分离测试,这似乎表明三元运算符也没有被完全处理。是的,这就是我通常结束时所做的,请参阅我在问题末尾的更新。我只是想知道我是否遗漏了什么,编译器是否真的是正确的(通常是关于这类错误!)@CristiDiaconescu:我认为编译器的正确之处在于它不知道它已经初始化了。如果你做了
bool val=true;字符串测试;如果(val){test=“hoorah!”;}控制台。WriteLine(测试)
然后编译器告诉您,
测试
没有初始化。我认为问题在于编译器认为if的结果可能是真的或假的。仅仅因为我们知道它将是假的,或者它将运行TryGetValue,并不意味着编译器看起来有那么远。我假设这是因为是一个编译器拒绝启动的兔子洞。@chris说的是我自己开始写的东西,尽管不那么连贯。我要补充的是,编译器不知道
TryGetValue
做什么,只是它返回一个布尔值,并传递一种ref。它不知道函数如何变异
val
reference@JoshE:不正确。编译器知道当函数调用返回时,所有out变量都将被初始化。这是out参数的要求(在从方法返回之前不将其赋值是编译器错误)。
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
c.PossiblyNullDictionary == null ? 
    false : 
    c.PossiblyNullDictionary.TryGetValue("someKey", out val) 
 //error: use of possibly uninitialized local variable