Why can't we define a variable inside an if statement?
Asked Answered
G

6

92

Maybe this question has been answered before, but the word if occurs so often it's hard to find it.

The example doesn't make sense (the expression is always true), but it illustrates my question.

Why is this code valid:

StringBuilder sb;
if ((sb = new StringBuilder("test")) != null) {
    Console.WriteLine(sb);
}

But this code isn't:

if ((StringBuilder sb = new StringBuilder("test")) != null) {
    Console.WriteLine(sb);
}

I found a similar question regarding a while statement. The accepted answer there says that in a while statement, it would mean the variable would be defined in each loop. But for my if statement example, that isn't the case.

So what's the reason we are not allowed to do this?

Gilbertina answered 2/7, 2011 at 19:52 Comment(6)
Variable declaration is a statement. Conditions require an expression that has a value, something that statements are not and doesn't have.Astrology
Jeff is spot on. Read up on the following two links. Statements: msdn.microsoft.com/en-us/library/ms173143(v=VS.100).aspx Expressions: msdn.microsoft.com/en-us/library/ms173144(v=VS.100).aspxStingy
@JeffMercado well, then change the spec to say that a variable declaration can be used as an expression. This is not a fundamental reason this can't be. C++ does it just like that.Bendicty
@usr: That almost happened in C#6 but was pulled out as far as I understood it in favor of building out a better spec for use with pattern matching. This will change in a future version of C#, but not possible in the current versions.Astrology
See #33180721 for why this was dropped for C# 6Illusionary
That you can't do this because variable declarations are a statement is just re-iterating the spec, not explaining it. The spec could just as easily have permitted a variable declaration expression that evaluates to the initialized value.Carvelbuilt
C
53

This is because section 8.5.1 of the C# language spec. states:

Furthermore, a variable initializer in a local variable declaration corresponds exactly to an assignment statement that is inserted immediately after the declaration.

This basically means that, when you do:

StringBuilder sb = new StringBuilder("test")

You're, in effect, doing the exact same thing as:

StringBuilder sb; sb = new StringBuilder("test")

As such, there is no longer a return value for your check against != null, as the assignment isn't a single expression, but rather a statement, which is a local-variable-declarator comprised of an identifier followed by an expression.

The language specification gives this example, stating that this:

void F() {
   int x = 1, y, z = x * 2;
}

Is exactly equivalent to:

void F() {
   int x; x = 1;
   int y;
   int z; z = x * 2;
}
Cohbert answered 2/7, 2011 at 20:2 Comment(6)
I guess a for loop is a special case then?Haynes
Strictly this is more saying HOW they have done it, than WHY. I see absolutely no reason why an "int x" could not leave the reference to the variable on the stack, that is "int x=1" could just as well be decoded logically as "(int x)=1" - with the new tuples in c#7.0 this is even more silly that it is NOT allowed (it is allowed if you declare ALL elements of the tuple though).Ragamuffin
@EskeRahn at the end of the day, every question like this has the same why - because the designers made this decision. This describes the decision and why the implementation has to be this way to match the spec.Cohbert
@ReedCopsey well that was an easy answer.... OFTEN a Why question like this can be answered with that it would somehow be conflicting with something else. This is NOT the case here, so here there are no reasons, though it can be excused why it is not (currently) allowed as you have done.Ragamuffin
@Haynes No. A for loop has a initialization statement, a condition expression, and an afterthought statement, and then the loop statement.Cousins
That the designers made this decision is not the why. That would be to say that the spec is the rationale for the spec. The why is the perceived benefits and consequences of the decision. We can't read the minds of the language designers, but we can examine the pros and the cons. The pro is obvious. No cons come to mind immediately.Carvelbuilt
H
139

Try C#7's Pattern Matching.

Using your example:

if (new StringBuilder("test") is var sb && sb != null) {
    Console.WriteLine(sb);
}
Hydr answered 20/7, 2018 at 19:59 Comment(5)
That does provide the syntax I asked for. Only downside is that the variable is not scoped to the if block, but it's defined for the same scope the if statement is in.Gilbertina
@Gilbertina The roslyn team notes for that choice from 7/15/16.Hydr
sly! neat! still, I'd prefer built-int first class if(var sb = F() ; sb != null) like in good old for(var sb = ; sb!=null ;)Vagary
also, oh my god, I understand pattern-matching against a concrete type, but pattern is var is just.. hilarious. It's almost as if someone specifically designed/allowed var type inference here exactly for that purposeVagary
If you use the distinct type (without ?) instead of var, and require nullable in your project, you can probably leave out the null check: if (new StringBuilder("test") is StringBuilder sb) { - because StringBuilder as used here is not a nullable type.Cowcatcher
C
53

This is because section 8.5.1 of the C# language spec. states:

Furthermore, a variable initializer in a local variable declaration corresponds exactly to an assignment statement that is inserted immediately after the declaration.

This basically means that, when you do:

StringBuilder sb = new StringBuilder("test")

You're, in effect, doing the exact same thing as:

StringBuilder sb; sb = new StringBuilder("test")

As such, there is no longer a return value for your check against != null, as the assignment isn't a single expression, but rather a statement, which is a local-variable-declarator comprised of an identifier followed by an expression.

The language specification gives this example, stating that this:

void F() {
   int x = 1, y, z = x * 2;
}

Is exactly equivalent to:

void F() {
   int x; x = 1;
   int y;
   int z; z = x * 2;
}
Cohbert answered 2/7, 2011 at 20:2 Comment(6)
I guess a for loop is a special case then?Haynes
Strictly this is more saying HOW they have done it, than WHY. I see absolutely no reason why an "int x" could not leave the reference to the variable on the stack, that is "int x=1" could just as well be decoded logically as "(int x)=1" - with the new tuples in c#7.0 this is even more silly that it is NOT allowed (it is allowed if you declare ALL elements of the tuple though).Ragamuffin
@EskeRahn at the end of the day, every question like this has the same why - because the designers made this decision. This describes the decision and why the implementation has to be this way to match the spec.Cohbert
@ReedCopsey well that was an easy answer.... OFTEN a Why question like this can be answered with that it would somehow be conflicting with something else. This is NOT the case here, so here there are no reasons, though it can be excused why it is not (currently) allowed as you have done.Ragamuffin
@Haynes No. A for loop has a initialization statement, a condition expression, and an afterthought statement, and then the loop statement.Cousins
That the designers made this decision is not the why. That would be to say that the spec is the rationale for the spec. The why is the perceived benefits and consequences of the decision. We can't read the minds of the language designers, but we can examine the pros and the cons. The pro is obvious. No cons come to mind immediately.Carvelbuilt
C
13

This has to do with the difference between a statement, and an expression. An expression has a value, whereas a statement does not.

Using your examples, notice these classifications:

StringBuilder sb; // statement

sb = new StringBuilder("test") // expression

StringBuilder sb = new StringBuilder("test"); // statement

Notice that only the middle portion is a expression.

Now we move onto your conditional statement. The syntax for using the not-equals operator is

expression != expression

So on both sides of the != you need something that actually has a value (this just makes sense). Ergo, you cannot have statements on either side of the operator. This is why the one version of your code works, while the other does not.

Class answered 2/7, 2011 at 20:3 Comment(0)
F
7

Instead of:

if ((StringBuilder sb = new StringBuilder("test")) != null) {
    Console.WriteLine(sb);
}

One could also write:

for (StringBuilder sb = new StringBuilder("test"); sb != null; sb = null) {
    Console.WriteLine(sb);
}

This for loop will execute once if your variable is not null. At the end of the loop, your temporary variable is set to null. The loop condition then evaluates to false, and the next statement continues after the closing brace is executed. Exactly as your if statement originally intended.

Fulks answered 4/3, 2018 at 21:20 Comment(1)
This is very similar to the question, but only experienced programmers will quickly spot what you are doing. However, an elegant solution.Candlelight
M
4

C# 7.0 introduced ability to declare out variables right inside conditions. In combination with generics, this can be leveraged for the requested result:

public static bool make<T> (out T result) where T : new() {
    result = new T();
    return true;
}
// ... and later:
if (otherCondition && make<StringBuilder>(out var sb)) {
    sb.Append("hello!");
    // ...
}

You can also avoid generics and opt for a helper method instead:

public static bool makeStringBuilder(out StringBuilder result, string init) {
    result = new StringBuilder(init);
    return true;
}
// ... and later:
if (otherCondition && makeStringBuilder(out var sb, "hi!")) {
    sb.Append("hello!");
    // ...
}
Medievalist answered 27/2, 2020 at 9:23 Comment(0)
C
0

I was redirected from another "duplicated" question: Declare variable in one line if statement . I do not think answers of this question can properly cover it.

If you are looking for a way to avoid repeating very long path and find it's impossible to define a variable inside if(), try also :

    var email = User.Current.Very.Complex.Path?.Email??"[email protected]";

it equals to:

    string email = null;
    if (User.Current.Very.Complex.Path != null)
         email = User.Current.Very.Complex.Path.Email
    if (email == null)
         email = "[email protected]";
Convincing answered 28/9, 2021 at 4:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.