Discussion: shortcomings of exception handling and failure reporting in C# #1019
Replies: 23 comments
-
I'm also annoyed by the "verbosity" of simple try/catch blocks, which even made me write the following: public static Boolean Try(params Action[] p)
{
foreach (Action a in p)
try
{
a();
}
catch
{
return false;
}
return true;
} There is no need to tell me this is a bad practice, I'm pretty aware of that. What I read most when talking about making parenthesis optional is that it can be ambiguous because there can be multiple try
M();
catch
try
N();
catch
C();
finally
A(); Can also be read as try
M();
catch
try
N();
catch
C();
finally
A(); Questions raised: would the compiler force you to use parenthesis or assume something? That ambiguity is bad. One thing I could think of is also making the M();
N();
catch A(); equals M();
try
{
N();
}
catch
{
A();
} and {
M();
N();
}
catch
A(); equals try
{
M();
N();
}
catch
A(); This one is tricky: M();
catch
N();
finally
C(); Equals try
{
M();
}
catch
{
try
{
N();
}
finally
{
C();
}
} To make the {
M();
catch
N();
}
finally
C(); That would let my first example to be written like this: M();
catch
N();
catch
C();
finally
A(); Which actually would be compiled as: try
{
M();
}
catch
{
try
{
N();
}
catch
{
try
{
C();
}
finally
{
A();
}
}
} That approach is even shorter and less ambiguous if you realize the simplicity of the rule. If it's a better approach or not is not for me to judge, I'm just sharing my thoughts. I hope we can find a way to make our lives easier while coding, without making it harder for other people. Best regards! |
Beta Was this translation helpful? Give feedback.
-
I think that this looks pretty good. Could eliminate a few more newlines as well for faster code. public static Boolean Try(params Action[] p)
{
foreach (Action a in p)
try { a(); }
catch { return false; }
return true;
} |
Beta Was this translation helpful? Give feedback.
-
@HaloFour I think more than anything people get annoyed by the braces but for these simple cases the |
Beta Was this translation helpful? Give feedback.
-
Indeed, but my point is that even today when the braces are required they don't force you to layout your code in such a manner. Allman, K&R, etc., they're just guidelines. I don't find it to be a sin to elect a different indentation, or even none at all, where it would benefit the legibility of the code. Simple Thank IPU that C# is not a whitespace sensitive language and take advantage of that fact. |
Beta Was this translation helpful? Give feedback.
-
@HaloFour The most compelling need to me is an expression form. var t1 = T() catch C();
var t2 = T() catch (IOException ex) C(ex); There may need to be limits (e.g. one |
Beta Was this translation helpful? Give feedback.
-
Quoting myself from #989
Another thing I shall add, nested try blocks looks very complex and kind of enforces indentation. Then to make code simpler, we consolidate multiple try blocks into one with all the catches together. |
Beta Was this translation helpful? Give feedback.
-
@HaloFour several people uses auto format features like resharper, which will ignore any "out of the box indentation" |
Beta Was this translation helpful? Give feedback.
-
Sounds like a bug that should be fixed with Resharper. Visual Studio is perfectly happy to leave braces and statements on a single line. |
Beta Was this translation helpful? Give feedback.
-
@HaloFour it's not a bug, it's a feature! 😄 |
Beta Was this translation helpful? Give feedback.
-
I think @HaloFour's proposal of "catch patterns" would cover that: var t2 = switch (T())
(
case var t: t,
catch (IOException ex) : C(ex);
); |
Beta Was this translation helpful? Give feedback.
-
How about new syntax, that avoids backward compatibility issues and avoids nesting to allow blocks to not be used: try catch
M();
catch
N();
catch
C();
finally
A(); The rules behind it being:
This could also be provided in expression form: var x = try catch (
M(),
catch (SomeException e when e.Foo) N(),
catch (SomeException e) C()
); |
Beta Was this translation helpful? Give feedback.
-
@DavidArno I don't think that |
Beta Was this translation helpful? Give feedback.
-
@DavidArno The |
Beta Was this translation helpful? Give feedback.
-
I think you are right. Re-reading it, it looks like |
Beta Was this translation helpful? Give feedback.
-
I really dislike the concept of suggesting that the language should change its grammar in order to satisfy the OCD of third-party tools (or developers). I'm surprised that R# is so strict about this. I've not used it much in Visual Studio because frankly I think it renders Visual Studio nearly unusable from a performance standpoint, but I've done a bit of Java in IntelliJ and that editor would even go so far to reformat the appearance of code (not the actual code) within the editor to collapse small definitions into a single line because it frankly reads a lot better that way.
How do you propose the syntax rules for that? What would follow
That sounds like a big departure from how exception handling works in any language I've seen. Why should statements in the |
Beta Was this translation helpful? Give feedback.
-
Why is that a workaround? F# exception handling is syntactically similar, although F# separates if via let t2 = try
T()
with
| :? System.DivideByZeroException => C(ex) F# does separate var t2 = try (T()) (
case Exception ex: C(ex)
); But then you'd have to nest |
Beta Was this translation helpful? Give feedback.
-
@HaloFour I like the FWIW I think |
Beta Was this translation helpful? Give feedback.
-
Not sure it would be half. I haven't looked at the spec to be sure. As other rules exist around which statements can appear at a given point, eg with expression bodies, then it may not be a big job. Or it could be huge. I was just throwing a radically different idea at the wall to see if it would stick.
Sounds like a good reason to me: C# can be first with something! 😉
They'd be implicitly caught as the lowering would involve adding a It's an idea for radically reducing the syntax of nested |
Beta Was this translation helpful? Give feedback.
-
|
Beta Was this translation helpful? Give feedback.
-
@orthoxerox In the second case, type inference is often lost, which is another point.
As I have said, I like the separation of |
Beta Was this translation helpful? Give feedback.
-
Can currently exception filter or pattern match (and squidge lines together) Filtering public int Test2()
{
try { return Method(); }
catch (DivideByZeroException) { return 0; }
catch (IOException e) when (e.Message == "Nah") { return 2; }
catch (IOException e) { throw new InvalidOperationException(e.Message, e); }
catch (InvalidOperationException) { return 3; }
} Pattern matching public int Test()
{
try { return Method(); }
catch (Exception ex) {
switch (ex) {
case DivideByZeroException e: return 0;
case IOException e when e.Message == "Nah": return 2;
case IOException e: throw new InvalidOperationException(e.Message, e);
case InvalidOperationException e: return 3;
default: throw;
}
}
} |
Beta Was this translation helpful? Give feedback.
-
That was "inspired" by F#'s asynchronous workflows... 😀 |
Beta Was this translation helpful? Give feedback.
-
@DavidArno Well, yes but still |
Beta Was this translation helpful? Give feedback.
-
Lately there've been several proposals about simplifying try-catch statements:
Rather than concentrating on discussing concrete solutions, I want to discuss the problems we're trying to solve. Feel free to comment on what you find annoying about exception handling in C#.
try/catch is very wordy when you're handling individual throwing statements
This is especially annoying when you use Allman style braces, you get six lines of noise per call:
try/catch is exceptionally wordy when you want a fallback value out of a throwing expression
Similar to
TryX
methods beforeout var
, this is one of the places where you cannot usevar
:throwing is the only standard way to signal failure other than
TryX
There's no expression-friendly way to signal failure that will be understood by the consumers of your library. If failure is normal and expected, you can use
TryX
pattern, but even without var
it will interrupt the control flow.If failure is abnormal, but still possible, you're expected to throw an exception, which will have to be caught and handled by the consumer of your library. You might hate catching exceptions in your code when using external libraries, but you're forced to throw them as the library writer.
Do you have anything else to add?
Beta Was this translation helpful? Give feedback.
All reactions