模式匹配变量范围

Pattern match variable scope

Roslyn Pattern Matching spec 中指出:

The scope of a pattern variable is as follows:

If the pattern appears in the condition of an if statement, its scope is the condition and controlled statement of the if statement, but not its else clause.

但是最新的 Microsoft "What's new" posts and presentations 正在显示此示例:

public void PrintStars(object o)
{
    if (o is null) return;     // constant pattern "null"
    if (!(o is int i)) return; // type pattern "int i"
    WriteLine(new string('*', i));
}

显示在模式匹配的 if 级别范围之外使用的模式匹配 i 变量。

这是疏忽,还是规范中的范围界定发生了变化?

来自同一文档:

the variables introduced by a pattern – are similar to the out variables described earlier

实际上这段代码:

if (!(o is int i)) return; // type pattern "int i"

大致等于:

int i;
if (!(SomeParsingOn(o, out i))) return; // type pattern "int i"

这意味着 i 声明在与 if 相同的级别上,这意味着它不仅在 if 的范围内,而且在以下语句的范围内。当您复制 if:

时可以看出这是真的
if (!(o is int i)) return; // type pattern "int i"
if (!(o is int i)) return; // type pattern "int i"

给出 错误 CS0128:一个名为 'i' 的局部变量已经在此范围 .

中定义

我将 a similar question 发布到 Roslyn 问题,DavidArno 给出了答案:

It's long, but you can read all the gory details of why the language design team chose to "enhance" the language in this way at #12939.

TL;DR you aren't alone in thinking the change unintuitive and contradictory to the way scope have worked before. The team sadly don't care though and the change is here to stay.

似乎已决定应用此范围,因此该规范现在已过时,遗憾的是,此范围将保留下来:

Option 3: Expression variables are scoped by blocks, for, foreach and using statements, as well as all embedded statements:

What is meant by an embedded statement here, is one that is used as a nested statement in another statement - except inside a block. Thus the branches of an if statement, the bodies of while, foreach, etc. would all be considered embedded.

The consequence is that variables would always escape the condition of an if, but never its branches. It's as if you put curlies in all the places you were "supposed to".

Conclusion

While a little subtle, we will adopt option 3. It strikes a good balance:

It enables key scenarios, including out vars for non-Try methods, as well as patterns and out vars in bouncer if-statements. It doesn't lead to egregious and counter-intuitive multi-level "spilling". It does mean that you will get more variables in scope than the current restrictive regime. This does not seem dangerous, because definite assignment analysis will prevent uninitialized use. However, it prevents the variable names from being reused, and leads to more names showing in completion lists. This seems like a reasonable tradeoff.