Replies: 12 comments 102 replies
|
I believe checked exceptions are generally bad. I recommend reading this interesting discussion with Anders Hejlsberg explaining why C# does not have them. http://www.artima.com/intv/handcuffs.html |
|
It is unlikely we'd reverse ourselves on such a longstanding and deep design issue. |
|
You should also read http://joeduffyblog.com/2016/02/07/the-error-model/ It will give a wide view about error communication in general: checked exceptions, unchecked exceptions, error codes, fail fast and how it is done in other languages. It also present a different solution with contracts, which have a proposal in here #105 Worth reading! A lot of knowledge in one place. |
|
I have to support the ticket author. When I read articles and comments arguing against checked exceptions, they remind me much of the arguments of dynamic typing vs static typing. When I call a method in C# it’s obvious what it’s return type will be. But I have no idea if it may raise an exception. So to be 100% sure I would need to put a try catch statement araound a lot of calls. Checked exception and static typing have a lot similarities. Pro
A good practise is to write code, which is easy to reason about it’s behaviour. I respect mr Heilsbergs opinion a lot, but C# was designed a long time ago and apparently a design goal was to make it easier for people switching to C#. That shouldn’t hinder us today to think about possibilities to make our code more expressive and therefore our work easier. And maybe also have a different opinion than a very smart person. PS: I remember the days when (also smart) people where arguing against static typing and emphasising the benefits of dynamic typing. |
|
Checked exceptions as they are implemented in a language like Java don't make API evolution difficult, they make it impossible. The exceptions are a part of the contract and it's impossible to add any others without breaking all existing consumers. So that forces implementers to group exceptions in weird ways, or just default to throwing everything as unchecked, which is insanely common. Forcing every consumer of every method to either catch/handle all of the possible exceptions or explicitly propagate them up doesn't scale. And the entire concept of checked exceptions breaks down very painfully when you start to program in a more functional manner. I think it's telling that nearly every other language on the JVM just ignores checked exceptions outright. If C# were to ever get something like checked exceptions I could only ever see them handled like NRTs. External metadata that can be used to drive analyzers which might warn, but never anything stricter. A much better solution would be DUs and |
|
Most of the references about the Checked Exception in C# are really old, without considering the new methodologies and design patterns, and around 99% of them say that this functionality/feature/design is bad. On one hand, we want to have a clean and reliable code, and On another hand, we say the maintenance of the code will be hard using Checked Exception. I believe the Checked Exception is good, especially when we are working on a class library as an external module for other systems. Each application that wants to use the class library, should know about the possible exceptions that may arise from the methods in our class library and handle them in the right way without needing trial and error. It is most useful when we want to write a unit test for our method, and there we can handle all possible exceptions with less concern about the runtime exceptions.
For the first case, it is really easy and the users don't need to do anything, but they can remove the unwanted handled exceptions if they want. Another issue mentioned in the articles against Checked Exception is that developers will use a general exception handler to wrap all checked exceptions in one place, and it will make them useless. If we want to worry about whether the developer will follow the structure, then why do we have this amount of design patterns? The developer who doesn't care about the structure, won't follow the design patterns too. Then it's totally up to the developer how to deal with this feature. He/She can handle it in the right way to get a better result or can omit the exceptions by wrapping all of them in one general try-catch. As the final word, to handle the versionability issue and decrease its defects on the client's codes, we can use different severities for each checked exception. E.g. we can use severities like Information, Warning, Error, and Fatal, and the client will have a better vision about how to deal with each exception. You can read my article about the Checked Exception here: and the repository to the Checked Exception extension on C# using Roslyn |
I like this idea !! I discussed it long time ago, but all team members said me the same as they said to you ... The issue is that specifying exactly exception will not make code hard to maintain ... but all team members do not understand that Checked exception could be implemented like this: public class FileUtils
{
public void Write(string content)
throws // Said that this function just throw some exception
{ ...
}
public void WriteGeneric<T>(string content)
throws // Said that this function just throw some exception
where T: class {
...
}
}In such case it could add support for error if user forgot to handle all error, it will feels like Rust (errors), Swift (errors), Java (checked exceptions) ... |
|
If possible exceptions is unchecked, then nobody can tell whether a method throws exceptions, so any method caller should initialize their local variable use |
|
The real problem is not Checked Exceptions, but actually the try-catch pattern in the first place. Or to express it somewhat stronger:
But the real solution is actually the way how a language like Rust solves it. making success or failure state part of the return type of a method, using a Result<T, E> type, based on the underlying concept of so called discriminated unions (but note Rust calls them enum). This however is basically too late for C#. I am aware of that. |
|
The advantage of checked exceptions is that thrown exceptions are part of the API, so you force callers to acknowledge it (either by handling or make it part of the caller method API). This helps prevent a lot of errors in code, but the way it's implemented in Java is not the best. Ideally, the compiler would implicitly detect and declare for you the exact
// this flag is implied by default if absent
#exceptions public
...
// explicit `throw ...;` statements are always in API automatically
// with `may not throw`, no additional exceptions can bubble up
// this disallows even unhandled exceptions in code itself, like null ref, out of range, class cast, etc.
// as well as exceptions that may be thrown from method calls, as per their API
void f() may not throw { ... }
// allow only these additional exceptions to implicit bubble up
void f() may throw A or B or ... { ... }
// allow any exception to bubble up; `A|B|...` is implicitly auto-filled by compiler and is part of the API
// when you hover an `f()` call in IDE, it shows the specific types of exceptions it may throw
// as if it had been declared explicitly in the function signature
void f() may throw { ... }
// non-publicly visible: implies `may throw` by default; compiler auto-fills API
internal void f() { ... }
// visible outside dll: implies `may not throw` by default;
// coder must handle bubbling exceptions, or declare them to ensure public API is correct
public void f() { ... }With // implies `may not throw` on any accessibility
// coder must always define bubbling exceptions (either explicitly per-exception, or with a `may throw`)
internal void f() { ... }With // exception is unspecified in public API,
// so if this dll is referenced by another project in a checked exception context,
// it assumes it `may throw Exception`
public void f() { ... }Of course, the list of exceptions in the API never includes unforeseeable crashing errors that might happen, like stack overflow not thrown by user code, etc., since those aren't expected to be caught. So, with this proposal, you'd get checked exceptions, but by default you don't even need to worry about adding |
|
The general motivation is to avoid exceptions, and only face them during prototyping where you're probably mishandling an API, or entering some dark documentation zones. This sounds utopic, but checked exceptions wouldn't truly add much value. The XML documentation containing the exceptions that may be thrown does a good enough job, except if you're not using Rider. Unfortunately as of the time of writing, VS doesn't seem to respect exception tags in members much, failing to show the description of the specific exception. Overall, in the deployed product, you should avoid exceptions being thrown as much as possible, and design your API in an exception-free manner by offering types wrapping potential erroneous cases, or using The takeaway is, with this feature you gain value by not following better architectural practices. By notifying the user about the types of exceptions to expect, you're telling them that they must play around funky exceptions when all they want is to use your API, and your API should fail to work when something's not right. |
|
The ergonomics of this design space has been solved by algebraic effects (e.g. Koka, Verse). A lot of the objections I see up above (e.g. higher-order functions) are addressed by the literature on AE from the past decade. Looks like Epic will be pushing Verse heavily with UE6, so a lot of people are soon going to encounter effects head-on. @geekley , you were on your way to re-invent a subset of AE. Let's explore what this would look like in C# for exception propagation alone. The syntax below is provisional and only intended to illustrate the model. This would track only exceptions participating in the checked-exception system; exceptions originating from the runtime remain untracked under normal C# semantics. Effect parameters such as This is also separate from enforcement policy. The compiler can track and propagate exception effects regardless of whether an unhandled effect is ignored, produces a warning, or is an error. Start with the simple case. Assume string ReadFile(string path)
throws IOException;A caller propagates that effect: Config LoadConfig(string path)
throws IOException
{
return Parse(ReadFile(path));
}The compiler can infer the effect from the implementation. Intermediate callers do not need to explicitly acknowledge or handle every exception; they can simply acquire the inferred effect row. Handling the exception removes it from the propagated effect row: Config LoadConfig(string path)
{
try
{
return Parse(ReadFile(path));
}
catch (IOException)
{
return Config.Default;
}
}Exception inheritance works naturally here. An effect for A narrower catch does not: string ReadConfig(string path)
throws IOException
{
try
{
return ReadFile(path);
}
catch (FileNotFoundException)
{
return "";
}
}
Multiple unrelated exceptions form an effect row: Config LoadConfig(string path)
throws IOException, FormatException;Rows can be normalized through exception inheritance. For public API contracts, declared effects can act as upper bounds. An implementation declared The next problem is delayed execution. Suppose we store a computation to run later: class Lazy<T, effect E>
{
private Func<T> throws E factory;
T Value() throws E;
}Constructing More generally, when an effectful delegate escapes its immediate call site and is stored for later execution, that effect information needs to survive with the stored value. Exposing an effect parameter on the enclosing type is one way to do that. That leads naturally to higher-order functions: TResult Apply<T, TResult, effect E>(
T value,
Func<T, TResult> throws E func)
throws E;
LINQ combines both problems: higher-order functions and delayed execution. var contents = paths.Select(ReadFile);
Conceptually, and ignoring the details of the existing iterator interfaces: interface IEnumerable<out T, effect E>
{
IEnumerator<T, E> GetEnumerator();
}
interface IEnumerator<out T, effect E>
{
bool MoveNext() throws E;
}The point is not that
A terminal operation exposes the accumulated effects during enumeration: T[] ToArray<T, effect E>(
this IEnumerable<T, E> source)
throws E;So: var contents =
paths
.Select(ReadFile)
.ToArray();propagates the tracked exceptions from The same general model applies to Interfaces introduce another abstraction boundary: interface IReader
{
string Read() throws IOException;
}Here the interface contract provides an upper bound. An implementation may have the same or a narrower effect row, but cannot introduce tracked effects outside that contract. If preserving an implementation-specific effect is itself part of the abstraction, the interface can be effect-polymorphic: interface IReader<effect E>
{
string Read() throws E;
}Again, Inheritance follows the same substitutability rule: an override may narrow the effects promised by the base member, but not widen them. This is also where analyzer-only approaches become limited. The information has to survive through delegates, stored computations, interfaces and assembly boundaries, so downstream compilations need the effect information in metadata. Existing assemblies would not all need to acquire that metadata at once. An imported method without effect metadata can conservatively be treated as: string LegacyMethod() throws Exception;Here This preserves soundness at the cost of precision. In practice, useful adoption would likely require early annotations for much of the BCL, external annotation metadata, or some other mechanism for supplying more precise contracts for existing libraries. Precision improves as more APIs acquire effect information. Public and protected effect information would become part of the API contract. Inference is useful within an implementation, but changing a published method from Nullable reference types provide a useful precedent for the migration strategy: additional static type information can be inferred, emitted in metadata, and introduced incrementally while existing assemblies remain usable with reduced precision. The legacy-boundary semantics need not be identical to NRT; the relevant precedent is gradual, metadata-backed static typing over an existing ecosystem. None of this requires union types or This isn't a weekend project. It touches the type system, delegates, LINQ, iterators, Sources / prior art
|

Uh oh!
There was an error while loading. Please reload this page.
What about Checked Exceptions in C# like Java?
I think it would be helpful to expect all behaviors of the method/object.
It will enforce that every method who calls FileUtils.Write should do it within a try..catch.. statement.
All reactions