Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
212 views
in Technique[技术] by (71.8m points)

c# 7.0 - Non-shortcircuting boolean operators and C# 7 Pattern matching

Im currently writing an C# application, targeting .NET 4.7 (C# 7). I am confused after I tried using the new way of declaring a variable utilizing the "is" keyword: if (variable is MyClass classInstance) This way it works, but when doing:

if (true & variable is MyClass classInstance)
{
    var a = classInstance;
}

Visual Studio (I'm using 2017) shows me the the Error Use of unassigned local variable 'classInstance'. Using the short-circuting version of & (&&) it works fine. Am I missing something about the & operator? (I know using the shortcircuting versions are much more commonly used, but at this point I'm just curious)

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

This one hurt my head, but I think I have got it figured out.

This confusion is caused by two quirks: the way the is operation leaves the variable undeclared (not null), and the way the compiler optimizes away Boolean, but not bitwise, operations.

Quirk 1. If the cast fails, the variable is unassigned (not null)

Per the documentation for the new is syntax:

If exp is true and is is used with an if statement, varname is assigned and has local scope within the if statement only.

If you read between the lines, this means that if the is cast fails, the variable is considered unassigned. This may be counterintuitive (some might expect it to be null instead). This means that any code within the if block that relies on the variable will not compile if there is any chance the overall if clause could evaluate to true without a type match present. So for example

This compiles:

if (instance is MyClass y)
{
    var x = y;
}

And this compiles:

if (true && instance is MyClass y)
{
    var x = y;
}

But this does not:

void Test(bool f)
{
    if (f && instance is MyClass y)
    {
        var x = y;  //Error: Use of unassigned local variable
    }
}

Quirk 2. Boolean operations are optimized away, binary ones are not

When the compiler detects a predestined Boolean result, the compiler will not emit unreachable code, and skips certain validations as a result. For example:

This compiles:

void Test(bool f)
{
    object neverAssigned;
    if (false && f)
    {
        var x = neverAssigned;  //OK (never executes)
    }
}

But if you use & instead of &&, it does not compile:

void Test(bool f)
{
    object neverAssigned;
    if (false & f)
    {
        var x = neverAssigned;  //Error: Use of unassigned local variable
    }
}

When the compiler sees something like true && it just ignores it completely. Thus

    if (true && instance is MyClass y)

Is exactly the same as

    if (instance is MyClass y)

But this

    if (true & instance is MyClass y)

Is NOT the same. The compiler still needs to emit code that performs the & operation and uses its output in a conditional statement. Or even if it doesn't, the current C# 7 compiler apparently performs the same validations as if it were. This may seem a little strange, but bear in mind that when you use & instead of &&, there is a guarantee that the & must execute, and though it seems unimportant in this example, the general case must allow for additional complexifying factors, such as operator overloading.

How the quirks combine

In the last example, the result of the if clause is determined at run time, not compile time. So the compiler can't be certain that y will end up being assigned before the contents of the if block are executed. Thus you get

    if (true & instance is MyClass y)
    {
        var x = y; //Error: use of unassigned local variable
    }

TLDR

In the situation of a compound logical operation, c# can't be sure that the overall if condition will resolve to true if and only if the cast is successful. Absent that certainty, it can't allow access to the variable, since it might be unassigned. An exception is made when the expression can be reduced to non-compound operation at compile time, for example by removing true &&.

Workaround

I think the way we are meant to use the new is syntax is as a single condition with an if clause. Adding true && at the beginning works because the compiler simply removes it. But anything else combined with the new syntax creates ambiguity about whether the new variable will be in an unassigned state when the code block runs, and the compiler can't allow that.

The workaround of course is to nest your conditions instead of combining them:

Won't work:

void Test(bool f)
{
    if (f & instance is MyClass y)
    {
        var x = y;  //Error: Use of unassigned local variable
    }
}

Works fine:

void Test(bool f)
{
    if (f)
    {
        if (instance is MyClass y)
        {
            var x = y;  //Works
        }
    }
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...