Roslyn analyzers for improving exception handling in C# code.
This project includes a set of diagnostics aimed at encouraging better exception practices. Each rule detects common pitfalls and helps improve reliability, maintainability, and clarity of your exception logic.
Ensures that exceptions are instantiated with a meaningful message.
❌ Bad:
throw new InvalidOperationException();
✅ Good:
throw new InvalidOperationException("Operation failed due to ...");
Avoids throwing base exceptions like System.Exception or System.SystemException.
❌ Bad:
throw new Exception("Something went wrong");
✅ Good:
throw new InvalidOperationException("Something went wrong");
Ensures that newly thrown exceptions inside catch blocks include the original exception as inner exception.
❌ Bad:
catch (Exception ex)
{
throw new CustomException("Something failed");
}
✅ Good:
catch (Exception ex)
{
throw new CustomException("Something failed", ex);
}
Preserves the original stack trace when rethrowing exceptions.
❌ Bad:
catch (Exception ex)
{
throw ex;
}
✅ Good:
catch (Exception ex)
{
throw;
}
Detects catch variables that are never used.
❌ Bad:
catch (Exception ex) { LogError(); }
✅ Good:
catch (Exception ex) { LogError(ex); }
Detects code written after a throw statement that will never execute.
❌ Bad:
throw new Exception();
DoSomething();
✅ Good:
DoSomething();
throw new Exception();
Detects try/catch blocks that don’t add meaningful handling logic.
❌ Bad:
try { DoSomething(); } catch (Exception) { throw; }
✅ Good:
DoSomething();
Ensures ThreadAbortException is either rethrown or reset.
❌ Bad:
catch (ThreadAbortException ex) { LogError(); }
✅ Good:
catch (ThreadAbortException ex) { Thread.ResetAbort(); }
Detects try blocks that are empty while having a catch.
❌ Bad:
try { } catch (Exception ex) { Log(ex); }
✅ Good:
try { DoSomething(); } catch (Exception ex) { Log(ex); }
Ensures proper exception handling for Task.WaitAll by requiring AggregateException catch or using Task.WhenAll.
❌ Bad:
try { Task.WaitAll(tasks); } catch (Exception ex) { Log(ex); }
✅ Good:
try { Task.WaitAll(tasks); } catch (AggregateException ex) { Log(ex); }
Detects catch blocks that are completely empty without even a comment.
❌ Bad:
try { DoSomething(); } catch (Exception) { }
✅ Good:
try { DoSomething(); } catch (Exception) { /* intentionally ignored */ }
Discourages throwing exceptions directly from property getters.
❌ Bad:
public string Name => throw new Exception();
✅ Good:
public string Name => _name ?? "";
Detects when ex.InnerException is thrown directly, which may cause null reference issues and loses the stack trace.
❌ Bad:
catch (Exception ex) { throw ex.InnerException; }
✅ Good:
catch (Exception ex) { throw; }
Suggests logging the full exception instead of just the message to retain full context.
❌ Bad:
LogError(ex.Message);
✅ Good:
LogError(ex);
Recommends logging the exception directly rather than calling ToString.
❌ Bad:
LogError("Error: " + ex.ToString());
✅ Good:
LogError(ex);
Detects cases where a new exception is thrown in a catch block without message or inner exception.
❌ Bad:
catch (Exception ex) { throw new Exception(); }
✅ Good:
catch (Exception ex) { throw new Exception("Something went wrong", ex); }
Detects when
filters on catch blocks that always return true and are thus redundant.
❌ Bad:
catch (Exception ex) when (true) { Handle(ex); }
✅ Good:
catch (Exception ex) when (ex is IOException) { Handle(ex); }
Detects throw new NotImplementedException()
left in methods or properties.
❌ Bad:
public void DoWork() => throw new NotImplementedException();
✅ Good:
public void DoWork() => ActualImplementation();
Detects general catch blocks that don’t include logging, rethrow, or even a comment.
❌ Bad:
try { ... } catch { }
✅ Good:
try { ... } catch { /* intentionally blank */ }
Ensures that exception types are declared public
to be visible when thrown or caught across assemblies.
❌ Bad:
class CustomException : Exception
{
}
✅ Good:
public class CustomException : Exception
{
}
Ensures that custom exceptions implement the expected constructors with message and inner exception parameters.
❌ Bad:
public class MyCustomException : Exception
{
}
✅ Good:
public class MyCustomException : Exception
{
public MyCustomException(string message) : base(message) { }
public MyCustomException(string message, Exception innerException)
: base(message, innerException) { }
}
Ensures that exception constructors pass their parameters (message, innerException) to the base constructor.
❌ Bad:
public MyCustomException(string message) { }
public MyCustomException(string message, Exception inner) { }
✅ Good:
public MyCustomException(string message) : base(message) { }
public MyCustomException(string message, Exception inner) : base(message, inner) { }
Ensures consistency and clarity by requiring exception classes to follow the naming convention of ending with 'Exception'.
❌ Bad:
public class MyCustomError : Exception
{
}
✅ Good:
public class MyCustomException : Exception
{
}
Flags catch blocks that handle fatal exceptions which should not be caught or are uncatchable.
❌ Bad:
try { ... }
catch (StackOverflowException ex) { Log(ex); }
✅ Good:
try { ... }
catch (Exception ex) { Log(ex); }
Sponsored by elmah.io.