Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Proposal] Try expressions #3756

Closed
1 of 4 tasks
nillkitty opened this issue Jul 30, 2020 · 3 comments
Closed
1 of 4 tasks

[Proposal] Try expressions #3756

nillkitty opened this issue Jul 30, 2020 · 3 comments

Comments

@nillkitty
Copy link

nillkitty commented Jul 30, 2020

Try Expressions

  • Proposed
  • Prototype: [Not Started]
  • Implementation: [Not Started]
  • Specification: [Not Started]

Summary

Allow an abbreviated version of the try syntax in cases in which you expect an exception to be thrown upon evaluation of an expression, but will not make any use of the Exception object besides potentially encapsulating it. The most basic try expression evaluates to a target-typed default if an exception is caught, while the full version leverages pattern-matching on the caught Exception object to determine the value the expression will return.

Examples:

/* Instead of ... */
IPAddress? ip = null;
try {
     ip = IPAddress.Parse(input);
} 
catch 
{
}

/* This would suffice... */
IPAddress? ip = try IPAddress.Parse(input);
/* If an exception was thrown, 'ip' is null (default value) here */

Another common example:

string? value = try Dictionary[key];
if (value is null) { return "Not found"; }  /* The key wasn't in the dictionary */

This works exceptionally well (no pun intended) when combined with the null coalescing operator to provide a safe fallback value:

XElement configRoot = try XElement.Parse(rawConfigXml) ?? new XElement("config");

... or in tandem with throw expressions when you wish to throw your own custom exception.

IPAddress ip = try IPAddress.Parse(input) ?? throw new ArgumentException("Invalid IP address", nameof(input));

If the caught exception's type needs to be examined to determine a proper fallback value, or if the exception is to be encapsulated in a new exception, the full version, which is similar to a switch expression can be used:

IPAddress ip = try (IPAddress.Parse(input)) catch
{ 
     FormatException ex => throw new ArgumentException("Invalid IP Address", nameof(input), ex),
     _ => IPAddress.Any
}

string? value = try (Dictionary[key]) catch 
{
     KeyNotFoundException => null,
     ArgumentNullException ex => throw ex,
     Exception ex => throw new ConfigurationException("Internal error accessing the configuration store", key, ex)
};

Motivation

  • Because a variable being assigned to during an assignment that may fail needs to be used later on, a traditional try block forces the declaration of the capturing variable to be outside the try block.
  • Currently, it is not valid to omit the catch block when you have no use for the thrown exception.
  • Currently, it is not valid to use an expression-bodied try statement.
  • While multiple successive catch blocks allow for pattern matching of the Exception object, it doesn't allow for the compact syntaxes allowed in switch expressions or even switch statements.
  • During various routine operations such as input validation, exceptions are expected but the exception objects caught are often unused in code besides potentially being encapsulated or used to determine what type of error occurred. Catch blocks in these circumstances often do not contain more than a throw or the assignment of an alternate value to the original variable.
  • All of the above contribute to needlessly long try/catch blocks just for the sake of catching parsing and validation errors.

Detailed design

In the above example, the expression try IPAddress.Parse(input) is compiled as if it were:

IPAddress? ip;
try {
     ip = IPAddress.Parse(input);
} 
catch { 
    ip = default;
}

Drawbacks

One drawback is that an exception could be thrown of a type other than the one the developer was intending to catch (and throw away), however this would have happened anyway if the developer is using catch { } or catch (Exception ex) { } today, as is often the case in the places this feature would most commonly be used.

Alternatives

Some other ways to reduce to the boilerplate associated with handling expected exceptions:

  • Allow the omission of a catch block in which case execution jumps beyond the try block when an exception is caught, however this effectively aborts the assignment rather than evaluating to the default value.
  • Allow expression-bodied try blocks, although this does not preclude the need to still declare the capturing variable outside of the scope of the try block.
@ufcpp
Copy link

ufcpp commented Jul 30, 2020

#2734

@PathogenDavid
Copy link

#2734

And also (mostly for the proposed alternatives): #2151, #1015, #616, #3383, #2914, #1914, #1068, #980, #220

Sort-of related: #1567, #908, #3666, #786

@333fred
Copy link
Member

333fred commented Jul 30, 2020

Closing as a duplicate.

@333fred 333fred closed this as completed Jul 30, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants