2

Maybe I am missing something obvious. Consider the following code:

string str;
try
{
    str = "";
}
catch (Exception)
{
    str = "";
}
finally
{
    Console.WriteLine(str);
}
Console.WriteLine(str); //this compiles

Here the compiler shows the well-known error CS0165: Use of unassigned local variable 'str'. I know I can fix it by doing string str = null. But, in which execution path does str might not be initialized?

halfer
  • 19,824
  • 17
  • 99
  • 186
taquion
  • 2,667
  • 2
  • 18
  • 29

1 Answers1

3

To give another way of looking at this that has nothing to do with threads: the compiler behaves this way because that's the way the language is specified.

From the ECMA C# 5 specification section 10.4.4.16:

Try-catch-finally statements

Definite assignment analysis for a try-catch-finally statement of the form:

try try-block
catch ( … ) catch-block-1
…
catch ( … ) catch-block-n
finally finally-block

is done as if the statement were a try-finally statement enclosing a try-catch statement:

try {
  try try-block
  catch ( … ) catch-block-1
   …
  catch ( … ) catch-block-n
}
finally finally-block

So how does a try-finally statement work in terms of definite assignment? That's in section 10.4.4.16:

For a try statement stmt of the form:

try try-block finally finally-block

  • ...
  • The definite assignment state of v at the beginning of finally-block is the same as the definite assignment state of v at the beginning of stmt.

So what does that mean in your case? At the start of the statement, your variable str is not definitely assigned... so by those rules, it's also not definitely assigned at the start of the finally block.

Now, why was the language designed that way? That's a slightly different question. I don't think that has anything to do with threads. The language generally assumes that anything can throw an exception. The only way a variable becomes definitely assigned is for it to be assigned a value and that assignment complete without throwing an exception. Any code path that could occur even if an exception occurs before assignment cannot be considered to definitely assign the variable.

As a simple example, imagine we changed your code to:

string str;
try
{
    str = MethodThatThrowsAnException();
}
catch (Exception)
{
    str = MethodThatThrowsAnException();
}
finally
{
    Console.WriteLine(str);
}

At that point, it doesn't seem so odd that str is not definitely assigned. It's only because it's assigning a string literal that it looks like it can't possibly fail. But I could imagine even assigning a string literal failing, if it's the first time that string constant has been seen, and it needs to allocate a String object... that allocation could fail. Then there are all the other ways that exceptions can be thrown, including threads being aborted.

All of this means:

  • The first statement in the try block can throw an exception
  • The first statement in the catch block can throw an exception

In that situation - however that occurs, and whether it's got anything to do with threading or not (it could be an allocation failure, for example) - you won't have executed any assignment to str, so it has no defined value to read.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Well now it seems this may be the correct answer... It makes sense and it is in the language specification – taquion Jun 16 '18 at 00:47
  • For sake of completeness I will take the task of joining both answers in one superb answer =)... just after diapers changing and tale counting =) ... Thanks both – taquion Jun 16 '18 at 01:43
  • @MichaelPuckettII: I still disagree that this needs to have anything to do with threads. All you need to assume is that any statement can throw an exception without completing. Even in C# were specified to only ever run in a single-threaded environment, that would be enough to force the language to be written that way. Threads being aborted is *one* way that a simple assignment can throw, but there are others. I think explaining it without threads is simpler and more general, personally. – Jon Skeet Jun 16 '18 at 07:49
  • @MichaelPuckettII: I've edited the answer to go more into the details of why the language specification assumes anything can throw. I'm not saying that threads being aborted isn't *one* way that we can end up with an exception in an unexpected way - but it's not the only way, and I think it leads to a more-complicated-than-required explanation. – Jon Skeet Jun 16 '18 at 07:56
  • @DaisyShipton I like your answer better. – Michael Puckett II Jun 16 '18 at 15:35
  • 1
    @taquion Apologies, but I'm asking you change your answer to Daisy's. I believe it is more relevant to your question where my answer, although relevant, may belong to a question more suited to threading. Threading or not, the bottom line is, when we hit try then finally will be called and if for any reason the assignment fails to be made in try or catch via exception or a new / repeat exception etc; finally will attempt to use the variable. If assigned in try and or catch we can't guarantee that code runs completely but we can guarantee finally... Even when threading. – Michael Puckett II Jun 16 '18 at 15:44