In C# and in Java (and possibly other languages as well), variables declared in a \"try\" block are not in scope in the corresponding \"catch\" or \"finally\" blocks. For e
If we ignore the scoping-block issue for a moment, the complier would have to work a lot harder in a situation that's not well defined. While this is not impossible, the scoping error also forces you, the author of the code, to realise the implication of the code you write (that the string s may be null in the catch block). If your code was legal, in the case of an OutOfMemory exception, s isn't even guaranteed to be allocated a memory slot:
// won't compile!
try
{
VeryLargeArray v = new VeryLargeArray(TOO_BIG_CONSTANT); // throws OutOfMemoryException
string s = "Help";
}
catch
{
Console.WriteLine(s); // whoops!
}
The CLR (and therefore compiler) also force you to initialize variables before they are used. In the catch block presented it can't guarantee this.
So we end up with the compiler having to do a lot of work, which in practice doesn't provide much benefit and would probably confuse people and lead them to ask why try/catch works differently.
In addition to consistency, by not allowing anything fancy and adhering to the already established scoping semantics used throughout the language, the compiler and CLR are able to provide a greater guarantee of the state of a variable inside a catch block. That it exists and has been initialized.
Note that the language designers have done a good job with other constructs like using and lock where the problem and scope is well defined, which allows you to write clearer code.
e.g. the using keyword with IDisposable objects in:
using(Writer writer = new Writer())
{
writer.Write("Hello");
}
is equivalent to:
Writer writer = new Writer();
try
{
writer.Write("Hello");
}
finally
{
if( writer != null)
{
((IDisposable)writer).Dispose();
}
}
If your try/catch/finally is hard to understand, try refactoring or introducing another layer of indirection with an intermediate class that encapsulates the semantics of what you are trying to accomplish. Without seeing real code, it's hard to be more specific.
Everyone else has brought up the basics -- what happens in a block stays in a block. But in the case of .NET, it may be helpful to examine what the compiler thinks is happening. Take, for example, the following try/catch code (note that the StreamReader is declared, correctly, outside the blocks):
static void TryCatchFinally()
{
StreamReader sr = null;
try
{
sr = new StreamReader(path);
Console.WriteLine(sr.ReadToEnd());
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
finally
{
if (sr != null)
{
sr.Close();
}
}
}
This will compile out to something similar to the following in MSIL:
.method private hidebysig static void TryCatchFinallyDispose() cil managed
{
// Code size 53 (0x35)
.maxstack 2
.locals init ([0] class [mscorlib]System.IO.StreamReader sr,
[1] class [mscorlib]System.Exception ex)
IL_0000: ldnull
IL_0001: stloc.0
.try
{
.try
{
IL_0002: ldsfld string UsingTest.Class1::path
IL_0007: newobj instance void [mscorlib]System.IO.StreamReader::.ctor(string)
IL_000c: stloc.0
IL_000d: ldloc.0
IL_000e: callvirt instance string [mscorlib]System.IO.TextReader::ReadToEnd()
IL_0013: call void [mscorlib]System.Console::WriteLine(string)
IL_0018: leave.s IL_0028
} // end .try
catch [mscorlib]System.Exception
{
IL_001a: stloc.1
IL_001b: ldloc.1
IL_001c: callvirt instance string [mscorlib]System.Exception::ToString()
IL_0021: call void [mscorlib]System.Console::WriteLine(string)
IL_0026: leave.s IL_0028
} // end handler
IL_0028: leave.s IL_0034
} // end .try
finally
{
IL_002a: ldloc.0
IL_002b: brfalse.s IL_0033
IL_002d: ldloc.0
IL_002e: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_0033: endfinally
} // end handler
IL_0034: ret
} // end of method Class1::TryCatchFinallyDispose
What do we see? MSIL respects the blocks -- they're intrinsically part of the underlying code generated when you compile your C#. The scope isn't just hard-set in the C# spec, it's in the CLR and CLS spec as well.
The scope protects you, but you do occasionally have to work around it. Over time, you get used to it, and it begins to feel natural. Like everyone else said, what happens in a block stays in that block. You want to share something? You have to go outside the blocks ...
In C++ at any rate, the scope of an automatic variable is limited by the curly braces that surround it. Why would anyone expect this to be different by plunking down a try keyword outside the curly braces?
The simple answer is that C and most of the languages that have inherited its syntax are block scoped. That means that if a variable is defined in one block, i.e., inside { }, that is its scope.
The exception, by the way, is JavaScript, which has a similar syntax, but is function scoped. In JavaScript, a variable declared in a try block is in scope in the catch block, and everywhere else in its containing function.
While in your example it is weird that it does not work, take this similar one:
try
{
//Code 1
String s = "1|2";
//Code 2
}
catch
{
Console.WriteLine(s.Split('|')[1]);
}
This would cause the catch to throw a null reference exception if Code 1 broke. Now while the semantics of try/catch are pretty well understood, this would be an annoying corner case, since s is defined with an initial value, so it should in theory never be null, but under shared semantics, it would be.
Again this could in theory be fixed by only allowing separated definitions (String s; s = "1|2";
), or some other set of conditions, but it is generally easier to just say no.
Additionally, it allows the semantics of scope to be defined globally without exception, specifically, locals last as long as the {}
they are defined in, in all cases. Minor point, but a point.
Finally, in order to do what you want, you can add a set of brackets around the try catch. Gives you the scope you want, although it does come at the cost of a little readability, but not too much.
{
String s;
try
{
s = "test";
//More code
}
catch
{
Console.WriteLine(s);
}
}
Like ravenspoint pointed out, everyone expects variables to be local to the block they are defined in. try
introduces a block and so does catch
.
If you want variables local to both try
and catch
, try enclosing both in a block:
// here is some code
{
string s;
try
{
throw new Exception(":(")
}
catch (Exception e)
{
Debug.WriteLine(s);
}
}