85

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?

comecme
  • 6,086
  • 10
  • 39
  • 67
  • 9
    Variable declaration is a statement. Conditions require an expression that has a value, something that statements are not and doesn't have. – Jeff Mercado Jul 02 '11 at 19:56
  • 4
    Jeff is spot on. Read up on the following two links. Statements: http://msdn.microsoft.com/en-us/library/ms173143(v=VS.100).aspx Expressions: http://msdn.microsoft.com/en-us/library/ms173144(v=VS.100).aspx – Khepri Jul 02 '11 at 19:59
  • 2
    @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. – usr Jun 05 '15 at 19:07
  • 2
    @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. – Jeff Mercado Jun 05 '15 at 19:13
  • 3
    See http://stackoverflow.com/questions/33180221/why-is-declaration-expression-dropped-in-c-sharp-6 for why this was dropped for C# 6 – Hound Aug 04 '16 at 12:44
  • 1
    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. – snarf Jul 10 '20 at 02:22

6 Answers6

123

Try C#7's Pattern Matching.

Using your example:

if (new StringBuilder("test") is var sb && sb != null) {
    Console.WriteLine(sb);
}
Jecoms
  • 2,558
  • 4
  • 20
  • 31
  • 8
    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. – comecme Oct 20 '18 at 15:26
  • 3
    @comecme The [roslyn team notes](https://github.com/dotnet/roslyn/issues/12939) for that choice from 7/15/16. – Jecoms Oct 20 '18 at 15:55
  • 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 ;)` – quetzalcoatl Jan 11 '19 at 11:37
  • 7
    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 purpose – quetzalcoatl Jan 11 '19 at 11:44
50

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;
}
Reed Copsey
  • 554,122
  • 78
  • 1,158
  • 1,373
  • 10
    I guess a for loop is a special case then? – MrPaulch Nov 09 '14 at 14:58
  • 11
    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). – Eske Rahn Mar 12 '17 at 14:02
  • 1
    @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. – Reed Copsey Mar 12 '17 at 17:37
  • 1
    @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. – Eske Rahn Mar 14 '17 at 15:54
  • 2
    @MrPaulch No. A for loop has a initialization statement, a condition expression, and an afterthought statement, and then the loop statement. – jv110 Mar 03 '18 at 21:41
  • 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. – snarf Jul 10 '20 at 02:33
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.

Ken Wayne VanderLinde
  • 18,915
  • 3
  • 47
  • 72
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.

Rand.Function
  • 770
  • 1
  • 7
  • 10
  • 1
    This is very similar to the question, but only experienced programmers will quickly spot what you are doing. However, an elegant solution. – Tore Aurstad Jun 21 '19 at 20:52
3

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!");
    // ...
}
YellowAfterlife
  • 2,967
  • 1
  • 16
  • 24
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??"default@mail.com";

it equals to:

    string email = null;
    if (User.Current.Very.Complex.Path != null)
         email = User.Current.Very.Complex.Path.Email
    if (email == null)
         email = "default@mail.com";
cheny
  • 2,545
  • 1
  • 24
  • 30