开发者

Moving "if" statement condition to a local variable makes C# compiler unhappy

Could you please explain me the reason of the following situation.

Today I wrote the code (only variables names are changed):

private void Foo() 
{
    int firstInteger, secondInteger;
    const string firstStringValue = "1", secondStringValue = "2";

    if (!string.IsNullOrWhiteSpace(firstStringValue) && int.TryParse(firstStringValue, out firstInteger) &&
        !string.IsNullOrWhiteSpace(secondStringValue) && int.TryParse(secondStringValue, out secondInteger))
    {
        // Using firstInteger and secondInteger here
        firstInteger++;
        secondInteger++;
    }
}

Everything was fine until I decided to move the if condition to a variable:

private void Foo()
{
    int firstInteger, secondInteger;
    const string firstStringValue = "1", secondStringValue = "2";

    bool firstIntegerAndSecondIntegerAreSpecified = 
        !string.IsNullOrWhite开发者_如何转开发Space(firstStringValue) && int.TryParse(firstStringValue, out firstInteger) &&
        !string.IsNullOrWhiteSpace(secondStringValue) && int.TryParse(secondStringValue, out secondInteger);

    if (firstIntegerAndSecondIntegerAreSpecified)
    {
        // Use firstInteger and secondInteger here
        firstInteger++;
        secondInteger++;
    }
}

Now the compiler underlines firstInteger and secondInteger variables with error "Local variable might not be initialized before accessing".

But why? The only thing I made is refactored the code a bit. And as I see it the logic is the same.


The compiler (or rather, the specification) doesn't spot the relationship between the value of firstIntegerAndSecondIntegerAreSpecified and the calls to int.TryParse. In the first form, it could work out that execution would only enter the body if both calls to int.TryParse had executed, and therefore both had definitely assigned values (due to the out parameters). Therefore there was no problem in the if block.

The rules for definite assignment don't cover the idea that firstIntegerAndSecondIntegerAreSpecified will only be true if both calls have been made, therefore in the body of the if block, the variables still aren't definitely assigned, hence the error.


The compiler is not built to be clever enough to figure out that a true value in the boolean variable ensures that the values in the integer are set. The compiler only tracks execution paths, not varaible values.

In the first case the compiler knows that it's impossible to enter the if statement without the TryParse calls setting the variables. In the second case the if statement is detached from the TryParse calls, so the compiler would have to track the variable value to figure out the relation.


In the first case, the compiler can look at the if condition and know that the body can't execute unless both of the variables have values (TryParse always sets its out parameter, even when it fails).

When you refactor that condition out to a variable, the compiler would have to trace that local variable to make sure that it can't be written to from when it's assigned to when it's used as the condition. Since the compiler isn't that clever, it can't be sure that your variables are initialized ("definitely assigned"), so it gives you an error.


The if statement can only be entered if both of the expressions are executed, but the bool can be assigned prematurely because of short-circuiting - if the first string fails to parse, the compiler skips parsing the second string, because regardless of the result, the bool's value must be false.


The compiler gives this error sometimes. Of course if you follow the logic of your code, you'll see that the variables are always initialized before use. But the compiler doesn't run the logic, it only acts according to some basic rules. And this is why it often gives this error even if in fact there is no error in the logic.

The solution is simple: just initialize all variables when you declare them.


Adding to Jon's answer -

It could be easier to handle both integers separately. And there is no need to check for NullOrWhiteSpace either.

int.TryParse(firstStringValue, out firstInteger)
    firstInteger++;

int.TryParse(secondStringValue, out secondInteger)
    secondInteger++;

You could even Extract this piece in a separate method

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜