Closed lachbaer closed 7 years ago
Wouldn't this procedure be practical possible?
No, a change like this can force developers to make changes to otherwise stable code which they aren't familiar with (often because it's been years since they looked at it). Changes like have a higher risk of introducing incorrect behavior, and increase the barrier to adoption of new releases of the compiler.
For years the answer was simply "there's nothing [practical] we can do about it", but with Roslyn that changed by the added support for custom analysis which can be selectively applied.
Well, a change of
try {
...
}
catch { }
to
try {
...
}
catch (Exception e) { e.GetBaseException(); }
in those cases would be easy, without any further side effects. One side effect though is that the programmer's eye is "catch
ed" to this. :wink:
@lachbaer I gave a link to a Rust page that shows what a match would look like with a Result type. I don't propose to give a C# version of that here as I think that's a much larger request ("Have great pattern matching in C#").
I embrace your intention but wholeheartedly disagree to drop catch
in anycase
I don't go against dropping bracket or empty catch
but it must be there. To make it work like ??
But I would support these syntax
var x = try SomeThing() catch default(T);
// catch specific, throw everything else
var x = try SomeThing() catch(ArgumentException ae) => default(T);
//
var x = try SomeThing() catch(Exception e) => {
return null; // multiline catch
};
Today I just came about the following:
I have UDPClient
and I want to just close the connection - no matter what. The code is:
try { _client.Close(); } catch (SocketException) { }
// Or if not in one line
try {
_client.Close();
}
catch (SocketException) { }
The documentation tells me that a SocketException
could be thrown, but not under what conditions. We should refer to the Winsockets API for that. Well, I think in this case it is really, really okay to do it like shown above and just ignore that exception.
Without any prejudice, here I know what I am doing and I would really like to have something like
try _client.Close();
without all the characters around the actual statement _client.Close()
.
You must admit, that try _client.Close()
really looks much, much better and cleaner.
I do not (any more) want to ease the ignorance of exceptions, but I am eager to hear your comments on this. ๐
@lachbaer
For the exceptionally rare occasion where it might even make sense the language already lets you. It doesn't warrant any shorthand. Personally, I'd still catch and at least log that exception, if not bubbling it up to the consumer to handle.
@HaloFour
For the exceptionally rare occasion where it might even make sense the language already lets you.
Lets me what? catch { }
? That's obvious.
@lachbaer,
I have
UDPClient
and I want to just close the connection - no matter what
Not sure if I'm missing something here. Surely you just call _client.Dispose()
when you want to close it, no matter what?
Dispose
can also throw an exception. In fact, according to Reference Source, Close()
just calls Dispose(true)
.
@yaakov-h,
Ah, OK. Thanks. Scrap that idea then ๐
@lachbaer Echoing @HaloFour, if you don't log that exception, you're throwing away valuable diagnostic information. There should not be an exception and it means you should figure out why it's happening and stop it.
Meanwhile I really wonder if I am so misled about exceptions?! ๐
As nearly every method, every constructor, etc. may throw exceptions, do really wrap every single call in a try
-catch
and catch every possible exception...?!?!?!?!?!?! ๐
@lachbaer,
No, that path leads to insanity.
What you do, is you write tests: tests for ideal situations and tests for situations that lead to exceptions and recovery. You write code that makes those tests pass. Finally, you add logging at the top level that captures unhandled exceptions. Then you release...
You examine the logs, you find exceptions. You work out what caused them, add new tests that fail under those conditions. You make the tests pass by changing the code. Then you release...
You examine the logs, you find exceptions...
Until the app is retired.
Well, guys (and galls). I just had a deeper peek into the Roslyn source code (not latest). What shall I say...?
There are quite a lot of catch-alls and even more one-liners! Just one example:
try
{
_symWriter.GetDebugInfo(ref debugDir, 0, out dataLength, IntPtr.Zero);
}
catch (Exception ex)
{
throw new PdbWritingException(ex);
}
src\Compilers\Core\Portable\NativePdbWriter\PdbWriter.cs(871)
Very often it is either a re-throwing, Logging, returning or setting something to null
. (I've also seen some catch { }
es!)
So, at last, it doesn't seem to be all that different in the real world than I thought. And therefore this discussion makes absolute sense!!!! Maybe not about ommiting the catch, but to "refresh" the try-catch-syntax a bit to make it more streamlined in the code.
And if even professional code looks like this, then please remember all us leisure programmers - who certainly don't do "red-green-refactor" in our freetime, but nevertheless want running code that's not going to be stopped by exceptions now and then.
There's a very big difference between wrapping an exception to maintain a consistent API surface and ignoring exceptions.
You still don't seem to have a solid case for any change other than that you don't seem to like catch
clauses very much.
By the way, it would already drastical reduce the number of lines just to omit the braces in try-catch-clauses, like it can be done with other constructs.
try
_symWriter.GetDebugInfo(ref debugDir, 0, out dataLength, IntPtr.Zero);
catch (Exception ex) throw new PdbWritingException(ex);
If you're trying to propose expression-bodied try/catch/finally clauses, then propose that. Ideally separately.
@yaakov-h
There's a very big difference between wrapping an exception to maintain a consistent API surface and ignoring exceptions.
The above is only a sample. As I said I also fould catch-and-ignore-patterns.
You still don't seem to have a solid case for any change other than that you don't seem to like catch clauses very much.
I don't like the syntactical overhead that comes with it! This overhead is one of the reasons for me to avoid exceptions and catches in the first place. (The second reason being that there are so many places throwing possible exceptions that I'm just too confused when to catch and when not - I cannot examine every single call, can I?)
If you're trying to propose expression-bodied try/catch/finally clauses, then propose that. Ideally separately.
I'm thinking about it. But not expression-bodied, until it is not an assignment (or alike).
@lachbaer
Omitting said braces make try
/catch
blocks completely ambiguous when nested. This is why it's one of the few cases where C# absolutely requires a block and not an embedded statement.
@HaloFour
I don't see any ambiguity when try
as first statement after try
, catch
and finally
is prohibited.
Otherwise you're probably going to have more than one statement anyway and you'll use a block.
Am 9. Mรคrz 2017 00:45:56 MEZ schrieb HaloFour notifications@github.com:
@lachbaer
Omitting said braces make
try
/catch
blocks completely ambiguous when nested. This is why it's one of the few cases where C# absolutely requires a block and not an embedded statement.-- You are receiving this because you were mentioned. Reply to this email directly or view it on GitHub: https://github.com/dotnet/csharplang/issues/220#issuecomment-285207586
try
if (true)
Foo()
else
try
DoTheThing();
catch (Exception1 e) // This one has to belong to the inner try
throw WrappedException(e);
catch (Exception2 e) // Ambiguous - which try does this belong to?
throw WrappedException(e);
finally // Still ambiguous down here
DoAnotherThing();
@yaakov-h
It is not ambigous if there are clear rules.
To me, number 3 is trouble-free possible.
Just have a look at if
-else
statements, where many else
statements can follow without blocks.
Additionally, any finally
will close the complete corresponding try
statement, because it always comes last.
With these rules the block above would be bound like
try
if (true)
Foo()
else
try
DoTheThing();
catch (Exception1 e) // This one has to belong to the inner try
throw WrappedException(e);
catch (Exception2 e) // This one also belongs to the inner try - like a 2nd `else`
throw WrappedException(e);
finally // This one belong to the inner try - like a 3rd `else`
DoAnotherThing();
catch (Exception3 e)
// This one belongs to the outer try, because
// previous `finally` has closed that `try` statement
DoCompletelyOtherThing();
This way blocks and one-liners could also be mixed up.
Of course I wouldn't recommend that style and use blocks everywhere instead in these cases. I wouldn't do it with if
-else
's also.
Also a one line assignment would be easily handled:
try var sr = new StreamReader("nonexistentfile.exe");
catch (ArgumentException ex) LogException(ex);
Because var sr
is not block scoped it is hoisted and belongs to the surrounding block.
In case of an ArgumentException
sr
is declared, but not initialized, leading to the same compiler complaints as
StreamReader sr;
try {
sr = new StreamReader("nonexistentfile.exe");
}
catch (ArgumentException ex) { LogException(ex); }
// sr might still be uninitialized here
@lachbaer
finally
clauses are completely optional. Unless you're implying that a non-block version of try
require one? But to do what? And why? And how is that somehow better than requiring a block?
There is absolutely nothing about the syntax which disambiguates the following two:
try
if (true)
Foo()
else
try
DoTheThing();
catch (Exception1 e)
throw WrappedException(e);
catch (Exception2 e)
throw WrappedException(e);
catch (Exception 3 e)
throw WrappedException(e);
try
if (true)
Foo()
else
try
DoTheThing();
catch (Exception1 e)
throw WrappedException(e);
catch (Exception2 e)
throw WrappedException(e);
catch (Exception 3 e)
throw WrappedException(e);
define the rules.
Simple rules were already defined back at C# 1.0 specifically to handle this. Require a block. It's quick and elegant and doesn't require modifying half of the grammar of the C# spec just to handle what happens to every single possible clause if it happens to be contained within a try
statement.
@HaloFour
To make this short, yes a finally;
would break the inner nested try
statement. finally
would be the only way to escape the nesting.
try
if (true)
Foo()
else
try
DoTheThing();
catch (Exception1 e)
throw WrappedException(e);
catch (Exception2 e)
throw WrappedException(e);
finally;
catch (Exception3 e)
throw WrappedException(e);
There can also be the rule that nested try
s within blockless statements are simply forbidden!
try
if (true)
Foo()
else
try // <---------- ERROR, nested try in blockless statement
DoTheThing();
catch (Exception1 e)
throw WrappedException(e);
catch (Exception2 e)
throw WrappedException(e);
catch (Exception 3 e)
throw WrappedException(e);
I have inspected the Roslyn code lately, because I think it is sure that the programmers who are working on it are professionals and their style is kind of representative. I found many, many, many places where blockless try
statements would shorten the code and make it a bit more readable - besides they enclose every single-liner within an block. But there weren't really any nestings that could be represented without blocks. All nested try
s were in many lines containing blocks. So I think it is for sure that the discussion of nesting is purely theoretical and it could be handled what is the easiest to implement.
But as there are others out there who love and do "blockless" coding, blockless try
statements would be quite an impovement! C# has gone its way towards them already, e.g. with expression-bodies and auto-properties.
And with todays omni-present syntax highlighting, coding fonts and automatic indention I don't see any actual disadvantages! (Remember, you're always free to block-wrap everything nevertheless, like the Roslyn team does)
@lachbaer,
I have inspected the Roslyn code lately, because I think it is sure that the programmers who are working on it are professionals and their style is kind of representative
Whilst the compiler team are obviously professionals, bear in mind that we all want a super-fast compiler, that doesn't throw exceptions at us. So I'd be cautious in viewing their style as representative of the best ways of writing code for situations other than those of similar need.
@DavidArno ๐คฃ
Whilst the compiler team are obviously professionals [...] I'd be cautious in viewing their style as representative of the best ways of writing code [...]
Don't let that hear @MadsTorgersen, @gafter et co. ๐ ๐
@lachbaer, @DavidArno. None of the three primary DotNet repositories (roslyn, corefx, or coreclr) follow a consistent coding style 100%.
However, that being said, we do have a coding style document that is probably the closest thing to the 'recommended' coding style guidelines: https://github.com/dotnet/corefx/blob/master/Documentation/coding-guidelines/coding-style.md.
But, it's your code and you're free to write it how you want. Just don't expect others to "enjoy" your code if it differs to far from the "common" practice.
@tannergooding,
Hey, that's not a bad set of guidelines. I agree with, and follow, > 90% of that. Only one I completely disagree with is number 10. I'm a strong proponent of "var
always, unless specifying the explicit type is absolutely needed". But life would be boring if I didn't disagree on something ๐
Personally, I prefer var everywhere as well, but I understand why they want it only where the type is obvious (since IntelliSense is not always available, such as in GitHub) ๐
@DavidArno var
is used in Roslyn all over the place, well, at least the parts that I've touched. ๐
Well that's disappointing. Where's the fun in us all agreeing? ๐
According to rule 1, there is absolutely nothing that stands again blockless try
-catch
-finally
.
This comment states that indention won't be a problem either.
If that is so, let's go and allow blockless try-statemens! ๐
BTW, my dislike for blocks also comes from using a German keyboard most of the time (I have a US version, too, but that is often hidden under lots of papers or somewhere else)
It is not fun to AltGr-7/8/9/0
(or Ctrl-Alt-7...
) while writing! And language switching with Alt-Shift
is exhausting... (@
and |
are no problem though when using two hands).
@lachbaer you should rebind the < >
key to be the { }
key instead.
@orthoxerox Do you have a link how to do so? Scancode remapping seems to work with whole keys only whereas { and } are 2 different keys on DE and EN keyboards.
Am 9. Mรคrz 2017 23:31:20 MEZ schrieb orthoxerox notifications@github.com:
@lachbaer you should rebind the
< >
key to be the{ }
key instead.-- You are receiving this because you were mentioned. Reply to this email directly or view it on GitHub: https://github.com/dotnet/csharplang/issues/220#issuecomment-285504824
Things we make simple and codify into the language should be things we recommend as 'best practices'. This proposal concerns me because it makes patterns that are traditionally deeply frowned upon elevated to a first-class construct.
Sometimes, if someone is doing something bad, it should be a lot to write. That's a good way to help hammer home hte idea of "you should not do this". If we make this a first-class construct, then the messaging becomes super-unclear. We would not have a super simple way for people to do something which then every piece of advice/tooling would then tell you to stop doing. That doesn't seem like a sensible or appropriate thing for us to be doing.
Note: what i would far prefer to see would be an adoption of the (value, error)
pattern that you see in languages like Rust. try
could then be something akin to:
var v = try OperationThatReturnsTupleOfResultAndError(...);
// this rewrites to:
var __t = OperationThatReturnsTupleOfResultAndError(...);
if (__t.error != null) return t;
var v = __t.value;
The explicit value/error tuple pattern (or 'Option' pattern) or bool/result pattern can be quite nice in many APIs. But you def need certain operators to make common cases and control flow easy to do instead of forcing everyone to use much more verbose and unweildy pattern matching and whatnot for the common code constructs.
@tannergooding var
would still be obvious on one line try catch. In the same manner as ?:
or ??
var x = try CalculateInt() catch(Exception e) 0;
Similar to
var x = TryCalculateInt() ?? 0;
int? TryCalculateInt()
{
try { return CalculateInt(); }
catch(Exception e) { return null; }
}
@Thaina I think that it's nicer to compile what you suggested into something like this:
var x = try CalculateInt() ?? 0;
Just because we're not catching anything.
When we actually want to catch something it might make sense to have this:
var x = try CalculateInt() catch(Exception e) throw e; // Do something with the exception
@CyrusNajmabadi
This proposal concerns me
We have already discarded the initial proposal (besides, the topic is renamed to "Discussion"). There seems to be broad consent on simply not allowing catch-all in any further ways than it is now.
And I stick with my proposal to depracate catch
and catch (...) { }
(empty block, trivia only), e.g. with a default warning (or error) under "Options/Text Editor/C#/Code Style"
About the (value, error)
-pattern, it reminds me of On Error Resume Next
error checking in VB:
var __t = OperationThatReturnsTupleOfResultAndError(...);
if (__t.error != null) return t;
var v = __t.value;
Dim m_t as Variant
On Error Resume Next
m_t = OperationThatReturnsValueAndPossiblyFailsWithError(...);
If Err.Number <> 0 then
Return t
End If
On Error Goto 0
Dim v as Variant
v = m_t
I don't like that ! And possibly most other participants here, too.
An even if that pattern would make it, the result should have its own type "ValueErrorTuple<T1, T2, ..., Exception>"
@eyalsk That's seem so wrong
try
is pair with catch
and should not be anything else. We should not expect try to emit null
As I always said that I go against dropping catch
in any case. I prefer empty catch
than other operator
But I have new idea now. What if we allow one line catch
to write in the same symbol as pattern matching match
?
var x = try CalculateInt() catch (
case WebException we: we.Code == 404 ? 0 : throw we
case NullReferenceException nre: 0
case Exception e: throw e
);
@eyalsk
var x = try CalculateInt() ?? 0;
That would actually be like the inital proposal here, with an implied catch-all and "do nothing".
@Thaina
var x = try CalculateInt() catch (
case WebException we: we.Code == 404 ? 0 : throw we; break;
case NullReferenceException nre: 0; break;
case Exception e: throw e; break;
);
That actually makes sense! ๐
@CyrusNajmabadi
Things we make simple and codify into the language should be things we recommend as 'best practices'. This proposal concerns me because it makes patterns that are traditionally deeply frowned upon elevated to a first-class construct.
But
try
{
}
catch
{
}
Is valid syntax from the start. So I never think empty catch
is bad by itself if we need only just that
@Thaina
So I never think empty catch is bad
Not in every case it is bad, but as a primary assumption it should be considered bad practice and still exists for historical reasons (and because sometimes, but rarely makes sense).
Is valid syntax from the start.
Yes. And it's very verbose and very easy to spot. It's not meant to be terse and easy to use. It's supposed to be something you fill out and 'do the right thing' with.
@lachbaer @CyrusNajmabadi
That's why I was proposing multiline catch
after one line try
. When we just really want default value for any case like HttpGet then it should be just
var res = try HttpGet(url) catch "";
And if we are in other place that should follow best practice then
var res = try HttpGet(url) catch(AllowedException e) {
//DoAnything
} "";
//or
var res = try HttpGet(url) catch (
case AllowedException isOK : ""
//for other case
default:throw e
);
I'd rather see us incorporate this into whatever 'match(expr)' work we're doing. i.e.:
var v = DoSomething() match (
case SomeType t: Something(),
case OtherType x: Whatever(),
catch (Exception e): AndSoOn());
@Thaina,
[try .. catch] is valid syntax. So I never think empty catch is bad
The following is valid syntax:
var x = (a:1 << 4 << 1 is var y ? y * 2 - 22 : 0, b:0).a;
But I suspect you'd consider it a bad way of assigning 42 to x
...
@DavidArno Well, I think your example is bad in general. It has no purpose to write like that at all. While empty catch
still has benefit where it should
Proposal:
try
expression withoutcatch
for inline useIntent
Sometimes it is not necessary to catch an exception, because you can either check side conditions or proceed though the expression is failed. In these cases it would be nice to skip the exception checking.
Example
In current code there would be something like
or even wrap it additionally with
This could be abbrevated and streamlined drastically with
The catching isn't necessary here, because a null-check (that should be done in
ProcessFile()
anyway) already cares about the failure. A completetry { } catch { }
is just unnecessary boilerplate.I guess that there are plenty of other useful scenarios where this would come in handy.
Other languages
PHP uses the
@
operator before an expression to suppress errors and warningsIn PHP
@
is used quite often to shorten things.Now, open for discussion... ๐