Skip to content

Enforce better exception handling in C#/.NET by ensuring exceptions are explicitly handled.

License

Notifications You must be signed in to change notification settings

marinasundstrom/CheckedExceptions

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Checked Exceptions for C#

Take control of exception flow β€” enforce explicit handling or declaration in C#

Build NuGet License

❓ FAQ β€’ πŸ§ͺ Sample project β€’ πŸ“š Documentation β€’ πŸ“ Change Log


Demo

Click the image to watch the video om YouTube.

Video

There are other videos in this playlist.


πŸš€ What It Does

CheckedExceptions is a Roslyn analyzer that makes exception handling explicit and reveals how exceptions propagate through your code.

If a method might throw an exception, the caller must either:

  • 🧯 Handle it (with try/catch), or
  • πŸ“£ Declare it (with [Throws(typeof(...))])

βœ… Inspired by Java’s checked exceptions
βš™οΈ Fully opt-in
πŸ’‘ Analyzer warnings by default β€” can be elevated to errors
πŸ“„ Supports .NET and third-party libraries via XML documentation
πŸ›  Includes code fixes to help you quickly handle or declare exceptions
βž• Supports .NET Standard 2.1


βœ… Quick Example

public class Sample
{
    public void Execute()
    {
        // ⚠️ THROW001: Unhandled exception type 'InvalidOperationException'
        Perform();
    }

    [Throws(typeof(InvalidOperationException))]
    public void Perform()
    {
        throw new InvalidOperationException("Oops!");
    }
}

βœ”οΈ Fix it by handling:

public void Execute()
{
    try { Perform(); }
    catch (InvalidOperationException) { /* handle */ }
}

Or by declaring:

[Throws(typeof(InvalidOperationException))]
public void Execute()
{
    Perform();
}

🧠 Why Use It?

  • Avoid silent exception propagation
  • Document intent with [Throws] instead of comments
  • Enforce better error design across your codebase
  • Works with unannotated .NET methods via XML docs
  • Plays nice with nullable annotations
  • Avoid confusing [Throws] with <exception> β€” enforce contracts, not just documentation

πŸ“¦ Installation

dotnet add package Sundstrom.CheckedExceptions

And define ThrowsAttribute in your project:

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Delegate | AttributeTargets.Property, AllowMultiple = true)]
public class ThrowsAttribute : Attribute
{
    public List<Type> ExceptionTypes { get; } = new();
    public ThrowsAttribute(Type exceptionType, params Type[] others) { … }
}

Find the full definition here.


βš™οΈ Configuration

.editorconfig

dotnet_diagnostic.THROW001.severity = warning
dotnet_diagnostic.THROW003.severity = warning

.csproj

<PropertyGroup>
  <WarningsAsErrors>nullable,THROW001</WarningsAsErrors>
</PropertyGroup>

JSON Settings

A baseline template is available in default-settings.json. For guidance on choosing the right default classification for your project, see the configuration guide.

The analyzer reads a single exceptions dictionary that explicitly classifies each exception type as Ignored, Informational, NonStrict, or Strict. Any exception not listed defaults to NonStrict, so an unclassified throw will trigger a low-severity diagnostic but won't require [Throws] or a catch. NonStrict exceptions remain part of analysisβ€”you may still declare or catch them, and doing so isn't considered redundant. Only exceptions classified as Ignored are completely filtered out.

Add CheckedExceptions.settings.json:

{
  // Default classification for exceptions not listed (default: NonStrict)
  "defaultExceptionClassification": "NonStrict",
  "exceptions": {
    "System.ArgumentNullException": "Ignored",
    "System.IO.IOException": "Informational",
    "System.TimeoutException": "NonStrict",
    "System.Exception": "Strict"
  },

  // If true, exceptions will not be read from XML documentation (default: false).
  "disableXmlDocInterop": false,

  // If true, analysis on LINQ constructs will be disabled (default: false).
  "disableLinqSupport": false,

  // If true, implicit exception inference in LINQ lambdas is disabled and declarations are required (default: false).
  "disableLinqImplicitlyDeclaredExceptions": false,

  // If true, no diagnostics will be issued on contract boundaries, such as arguments to methods and  return statements (default: false).
  "disableLinqEnumerableBoundaryWarnings": false,

  // If true, control flow analysis, with redundancy checks, is disabled (default: false).
  "disableControlFlowAnalysis": false,

  // If true, basic redundancy checks are available when control flow analysis is disabled (default: false).
  "enableLegacyRedundancyChecks": false,

  // If true, declaring [Throws(typeof(Exception))] acts as a catch-all and suppresses hierarchy redundancy checks; the base-type
  // warning (THROW003) remains unless "disableBaseExceptionDeclaredDiagnostic" is set (default: false).
  "treatThrowsExceptionAsCatchRest": false,

  // If true, the analyzer will not warn about declaring base type Exception with [Throws] (default: false).
  "disableBaseExceptionDeclaredDiagnostic": false,

  // If true, the analyzer will not warn about throwing base type Exception (default: false).
  // Set to true if you use another analyzer reporting a similar diagnostic.
  "disableBaseExceptionThrownDiagnostic": false
}

Migration note: Legacy ignoredExceptions and informationalExceptions settings are still recognized but have been deprecated. They are automatically translated to the unified exceptions map.

Control flow analysis powers redundancy checks (e.g. unreachable code, redundant catches, unused exception declarations). Disabling it may improve analyzer performance slightly at the cost of precision.

Register in .csproj:

<ItemGroup>
  <AdditionalFiles Include="CheckedExceptions.settings.json" />
</ItemGroup>

πŸ” Diagnostics

ID Message
THROW001 ❗ Unhandled exception: must be caught or declared
THROW002 ℹ️ Non-strict exception may cause runtime issues
THROW003 🚫 Avoid declaring exception type System.Exception
THROW004 🚫 Avoid throwing exception base type System.Exception
THROW005 πŸ” Duplicate declarations of the same exception type in [Throws]
THROW006 🧬 Incompatible Throws declaration (not declared on base member)
THROW007 🧬 Missing Throws declaration for base member's exception
THROW008 πŸ“¦ Exception already handled by declaration of super type in [Throws]
THROW009 🧹 Redundant catch typed clause
THROW010 ⚠️ [Throws] is not valid on full property declarations
THROW011 πŸ“„ Exception in XML docs is not declared with [Throws]
THROW012 🧹 Redundant exception declaration (declared but never thrown)
THROW013 🧹 Redundant catch-all clause (no remaining exceptions to catch)
THROW014 🧹 Catch clause has no remaining exceptions to handle
THROW015 🧹 Catch clause is redundant (General diagnostic)
THROW020 πŸ›‘ Unreachable code detected
IDE001 πŸ™ˆ Unreachable code (hidden diagnostic for editor greying)

πŸ›  Code Fixes

The analyzer offers the following automated code fixes:

  • βœ… Add exception declaration Adds [Throws(typeof(...))] attribute to declare the exception, or appends exception to existing attribute. (Fixes THROW001)

  • 🧯 Surround with try/catch Wraps the throwing site in a try/catch block. (Fixes THROW001)

  • βž• Add catch to existing try block Appends a new catch clause to an existing try. (Fixes THROW001)

  • 🧹 Remove redundant catch clause Removes a catch clause for an exception type that is never thrown. (Fixes THROW009, THROW013, THROW014)

  • πŸ”§ Add [Throws] declaration from base member Ensures overridden or implemented members declare the same exceptions as their base/interface. (Fixes THROW007)

  • πŸ”§ Add [Throws] declaration from XML doc Converts <exception> XML documentation into [Throws] attributes. (Fixes THROW011)

  • βž• Introduce catch clauses from rethrow in catch-all Appends new catch clauses before "catch all". (Fixes THROW001)


✨ Advanced Features

  • Lambdas, local functions, accessors, events – full support across member kinds
  • Exception inheritance analysis – understands base/derived exception relationships
  • XML documentation interop – merges [Throws] with <exception> tags from external libraries
  • Nullability awareness – respects #nullable enable context
  • Standard library knowledge – recognizes common framework exceptions (e.g. Console.WriteLine β†’ IOException)
  • Control flow analysis – detects whether exceptions are reachable, flags redundant catch clauses, and reports unreachable code caused by prior throws or returns

❓ Frequently Asked Questions (FAQ)

❓ How is this different from Java's checked exceptions?

Answer:

Java's checked exceptions are mandatory β€” the compiler enforces them, and every method must declare or handle them. While this promotes visibility, it also leads to friction, boilerplate, and workarounds like throws Exception.

This analyzer takes a modern, flexible approach:

  • ⚠️ Warnings by default, not errors β€” you’re in control.
  • ✍️ Opt-in declaration using [Throws] β€” only where it matters.
  • πŸ› οΈ Code fixes and suppression make adoption practical.
  • πŸ”„ Gradual adoption β€” use it for new code, leave legacy code untouched.
  • 🎯 Focused on intention, not obligation β€” you declare what callers need to know, not what int.Parse might throw.

βœ… Summary: This is exception design with intent, not enforcement by force. It improves exception hygiene without the rigidity of Java’s model.

❓ Can I use <exception> XML documentation tags instead of the [Throws] attribute?

Answer:

No β€” for your own code, <exception> tags are not treated as semantic declarations by the analyzer. While they are useful for documentation and IntelliSense, they are not part of the C# language’s type system and cannot be reliably analyzed or enforced.

Instead, we encourage and require the use of the [Throws] attribute for declaring exceptions in a way that is:

  • Explicit and machine-readable
  • Suitable for static analysis and enforcement
  • Integrated with code fixes and tooling support

🧩 Interoperability with external libraries

When analyzing external APIs (e.g., referenced .NET assemblies), we do recognize <exception> tags from their XML documentation β€” but only for interop purposes. That is:

  • We treat documented exceptions from public APIs as "declared" when [Throws] is not available.
  • This helps maintain compatibility without requiring upstream changes.

⚠️ Summary:
<exception> tags are respected for interop, but they are not a replacement for [Throws] in code you control.

❓ What about .NET Standard 2.0 support?

Answer:

The analyzer offers limited support for projects targeting .NET Standard 2.0. You’ll still get accurate diagnostics for your own code, as well as third-party libraries. However, members defined in the .NET Standard framework may not indicate which exceptions they throw.

This is due to a technical limitation: the XML documentation files for .NET Standard assemblies are often incomplete or malformed, making it impossible to extract reliable exception information.

βœ… Recommendation: Target a modern .NET SDK (e.g., .NET 6 or later) to get full analyzer support, including framework exception annotations.

❓ What about LINQ support?

Answer:

There is support for LINQ query operators on IEnumerable<T> and asynchronous operators like FirstAsync on IAsyncEnumerable<T> (via System.Linq.Async). Support for IQueryable<T> is enabled by default and can be disabled via the disableLinqQueryableSupport setting.

List<string> values = [ "10", "20", "abc", "30" ];

var tens = values
    .Where(s => int.Parse(s) % 10 is 0)
    .ToArray(); // THROW001: unhandled FormatException, OverflowException

Exceptions are inferred and implicit on LINQ methods. Any explicit [Throws] on LINQ lambdas is flagged as redundant. This behavior can be disabled.

Read about it here.


🀝 Contributing

  1. Fork
  2. Create feature branch
  3. Push PR with tests & documentation
  4. ❀️

πŸ“œ License

MIT

About

Enforce better exception handling in C#/.NET by ensuring exceptions are explicitly handled.

Topics

Resources

License

Stars

Watchers

Forks

Sponsor this project