9

Why if I write

void Main()
{
      string value = @"C:\";
      if (!string.IsNullOrEmpty(value))  { 
            string sDirectory = Path.GetDirectoryName(value);
      }

}

it compiles.

And in case if I write

void Main()
{
      string value = @"C:\";
      if (!string.IsNullOrEmpty(value))
        string sDirectory = Path.GetDirectoryName(value);


}

It doesn't ?

It's clear that from pure functional point of view the declaration of the variable in the second example is useless, but why it magically becomes usefull in first example, so ?

The IL code produced by both examples is exactly the same.

IL_0000:  ldstr       "C:\"
IL_0005:  stloc.0     
IL_0006:  ldloc.0     
IL_0007:  call        System.String.IsNullOrEmpty
IL_000C:  brtrue.s    IL_0015
IL_000E:  ldloc.0     
IL_000F:  call        System.IO.Path.GetDirectoryName

EDIT:

Forgot to mantion that to produce the IL code for the second case (so the case which is not compilable), it's enough to compile without string sDirectory =

Tigran
  • 61,654
  • 8
  • 86
  • 123

5 Answers5

20

The production for an if statement is in section 8.7.1 of the C# spec, and it goes like this:

if-statement:
    if   ( boolean-expression )   embedded-statement
    if   ( boolean-expression )   embedded-statement   else   embedded-statement

The start of section 8 of the C# spec explicitly talks about the embedded-statement production after giving the specification for it:

embedded-statement:
   block
   empty-statement
   expression-statement
   selection-statement
   iteration-statement
   jump-statement
   try-statement
   checked-statement
   unchecked-statement
   lock-statement
   using-statement 
   yield-statement

The embedded-statement nonterminal is used for statements that appear within other statements. The use of embedded-statement rather than statement excludes the use of declaration statements and labeled statements in these contexts. The example

void F(bool b) {
     if (b)
         int i = 44;
} 

results in a compile-time error because an if statement requires an embedded-statement rather than a statement for its if branch. If this code were permitted, then the variable i would be declared, but it could never be used. Note, however, that by placing i’s declaration in a block, the example is valid.

Note that an assignment counts as an expression-statement - but a local variable declaration doesn't. (That's a declaration-statement, as in section 8.5.)

In terms of a design decision, it makes no sense to declare a variable that you can't then use - so it's good that the compiler stops you from doing it.

Community
  • 1
  • 1
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Agree, it's absolutely good that compiler stops me in second case. My question was about the *difference* of the first and second, but this, probably, leads to the question why {} is perfectly compilable case. – Tigran Jan 11 '12 at 17:18
  • @Tigran: Hopefully my question answers the difference part - they're different types of statement, one of which is allowed as an embedded-statement, and the other of which isn't. – Jon Skeet Jan 11 '12 at 17:27
  • AS I wrote to David, the both cases are actually useless, and compiler is able to identify that , so why prefer one and not other? Why to make any difference between them ? That was *actually* the root of the question. – Tigran Jan 11 '12 at 17:29
  • 1
    @Tigran: The compiler is able to identify that you've got an unused local variable in the first case, as part of a block - but that's a *semantic* analysis rather than just *syntactic* analysis. – Jon Skeet Jan 11 '12 at 17:30
  • 5
    It's actually not *quite* correct for the spec to say that the variable could *never possibly* be used. `if (x) int i = M(out i) + i;` would both write and read `i`; such a local variable declaration is exceedingly odd, but legal. What it really should say to be accurate is that the value of i *as determined by the value of the initializer* can never be used. – Eric Lippert Jan 11 '12 at 17:42
  • Because an empty block is perfectly legal, in C, in C++ and in C#. – Nicholas Carey Jan 11 '12 at 18:42
  • @NicholasCarey: It's not clear to me which bit that's responding to. – Jon Skeet Jan 11 '12 at 18:49
  • @JonSkeet: Tigran's first comment, "...this,...leads to the question why {} is perfectly compilable case" – Nicholas Carey Jan 11 '12 at 19:37
6

Your second form tries to use what is effectively two statements (a variable declaration and a variable assignment) where only a single statement could be used. Think of it as:

if (!string.IsNullOrEmpty(value))
    string sDirectory;
    sDirectory = Path.GetDirectoryName(value);

You can see this won't compile!

David M
  • 71,481
  • 13
  • 158
  • 186
  • the *thing* is, that both cases **are** useless and compiler is able to understand it, so why prefer one and not other. – Tigran Jan 11 '12 at 17:20
5

The first version with the brackets declares a new local scope within which you declare a string variable, the second version doesn't - the variable declaration and assignment is interpreted as a single embedded statement which may not include variable declarations, hence the compilation error.

BrokenGlass
  • 158,293
  • 28
  • 286
  • 335
1
string value = @"C:\";
if (!string.IsNullOrEmpty(value))
string sDirectory = Path.GetDirectoryName(value);

The second statement is what is considered an embedded statement.. if you want to use sDirectory within what is called a "Code Block" wrap { } around it. inline statements like what you are trying to do in my opinion make for poor readability..

 string value = @"C:\";
 string sDirectory = string.Empty; should be even better way to code..
 if (!string.IsNullOrEmpty(value))
 {
     sDirectory = Path.GetDirectoryName(value);
 } 

or this

both cases now should compile

  string value = @"C:\";
  string sDirectory = string.Empty;
  if (!string.IsNullOrEmpty(value))
     sDirectory = Path.GetDirectoryName(value);
MethodMan
  • 18,625
  • 6
  • 34
  • 52
1

I am not sure for C#, but this is the case for other languages: because { } open a block and therefore a new scope. If you omit them, then the variable is being declared in the scope of Main and so will only be declared some of the time, and so the compiler doesn't know afterwards whether sDirectory exists or not.

Matt
  • 7,100
  • 3
  • 28
  • 58