This document describes the available code fixes and which diagnostics they apply to.
| Diagnostic ID | Description | Available Fixes |
|---|---|---|
| THROW001 | Unhandled exception type | 🔧 Add throws declaration 🧯 Surround with try/catch ➕ Add catch clause to surrounding try ➕ Introduce catch clause |
| THROW004 | Redundant typed catch clause | 🧹 Remove redundant catch clause |
| THROW005 | Duplicate exception declaration | 🗑️ Remove redundant throws declaration |
| THROW007 | Missing throws from base/interface | 🔧 Add throws declaration from base member |
| THROW011 | Missing throws from XML documentation | 🔧 Add throws declaration from XML doc |
| THROW012 | Redundant exception declaration | 🗑️ Remove redundant throws declaration |
| THROW013 | Redundant catch-all clause | 🧹 Remove redundant catch clause |
| THROW014 | Catch clause has no remaining exceptions to handle | 🧹 Remove redundant catch clause |
| THROW015 | Redundant catch clause | 🧹 Remove redundant catch clause |
| THROW017 | Deferred enumeration crossing boundary | 🧺 Materialize enumeration with ToArray |
A throwing site is either:
- A
throwstatement, or - A method, property, event, or LINQ operator access where exceptions are known to propagate (via
[Throws(...)]or built-in knowledge).
LINQ termination operators such as First or FirstAsync are therefore eligible for fixes like Add throws declaration when their query pipelines can throw.
Applies to:
THROW001– Unhandled exception type
Adds undeclared exception types to the [Throws] attribute of the declaring member—either creating a new attribute or updating an existing one.
// Before
public void Foo()
{
// THROW001: Unhandled exception type 'InvalidOperationException'
throw new InvalidOperationException();
}
// After
[Throws(typeof(InvalidOperationException))]
public void Foo()
{
throw new InvalidOperationException();
}// Before
public void Bar()
{
// THROW001: Unhandled exception type 'InvalidOperationException'
Foo();
}
[Throws(typeof(InvalidOperationException))]
public void Foo()
{
throw new InvalidOperationException();
}
// After
[Throws(typeof(InvalidOperationException))]
public void Bar()
{
Foo();
}
[Throws(typeof(InvalidOperationException))]
public void Foo()
{
throw new InvalidOperationException();
}// Before
public int Foo
{
get => throw new InvalidOperationException();
}
// After
public int Foo
{
[Throws(typeof(InvalidOperationException))]
get => throw new InvalidOperationException();
}// Before
public int Foo => throw new InvalidOperationException();
// After
[Throws(typeof(InvalidOperationException))]
public int Foo => throw new InvalidOperationException();Applies to:
THROW001– Unhandled exception type
Wraps the throwing site (and its directly related code) in a try/catch block.
⚠️ Notes:
- Not a silver bullet—manual follow-up may be required.
- In lambdas or expression-bodied members, the expression is automatically converted into a block-bodied form before wrapping.
// Before
int x = 2;
// THROW001: Unhandled exception type 'InvalidOperationException'
Foo(x);
[Throws(typeof(InvalidOperationException))]
public void Foo(int arg)
{
throw new InvalidOperationException();
}
// After
try
{
int x = 2;
Foo(x);
}
catch (InvalidOperationException invalidOperationException)
{
}
[Throws(typeof(InvalidOperationException))]
public void Foo(int arg)
{
throw new InvalidOperationException();
}// Before
Func<int, int> f = x => Foo(x); // THROW001
// After
Func<int, int> f = x =>
{
try
{
return Foo(x);
}
catch (InvalidOperationException invalidOperationException)
{
return default;
}
};Applies to:
THROW001– Unhandled exception type
Adds a missing catch clause for a specific exception type to an existing surrounding try.
// Before
try
{
Foo();
// THROW001: Unhandled exception type 'ArgumentException'
Bar(2);
}
catch (InvalidOperationException invalidOperationException)
{
}
[Throws(typeof(InvalidOperationException))]
public void Foo() { /* ... */ }
[Throws(typeof(ArgumentException))]
public void Bar(int arg) { /* ... */ }
// After
try
{
Foo();
Bar(2);
}
catch (InvalidOperationException invalidOperationException)
{
}
catch (ArgumentException argumentException)
{
}
[Throws(typeof(InvalidOperationException))]
public void Foo() { /* ... */ }
[Throws(typeof(ArgumentException))]
public void Bar(int arg) { /* ... */ }Applies to:
THROW001– Unhandled exception type
Prepends catch clause for a rethrown exception type in catch all.
// Before
try
{
// THROW001: Unhandled exception type 'InvalidOperationException'
Foo();
// THROW001: Unhandled exception type 'ArgumentException'
Bar(2);
}
catch
{
// THROW001: Unhandled exception type 'InvalidOperationException'
// THROW001: Unhandled exception type 'ArgumentException'
throw;
}
[Throws(typeof(InvalidOperationException))]
public void Foo() { /* ... */ }
[Throws(typeof(ArgumentException))]
public void Bar(int arg) { /* ... */ }
// After
try
{
Foo();
Bar(2);
}
catch (InvalidOperationException invalidOperationException)
{
}
catch (ArgumentException argumentException)
{
}
catch // This is left intentionally
{
throw;
}
[Throws(typeof(InvalidOperationException))]
public void Foo() { /* ... */ }
[Throws(typeof(ArgumentException))]
public void Bar(int arg) { /* ... */ }Applies to:
THROW004– Redundant typed catch clauseTHROW013– Redundant catch-all clauseTHROW014– No remaining exceptions to handle (for typed catch clause)THROW015– Redundant catch clause
Removes a redundant catch clause for an exception type that is not thrown in the current try block.
⚠️ Notes:
- In lambdas or expression-bodied members, removing the last
catchrestores expression form.- In statements, removing the last
catchremoves the entiretryand inlines the contents.
// Before
try
{
Foo();
}
catch (InvalidOperationException invalidOperationException)
{
}
catch (ArgumentException argumentException) // redundant
{
}
[Throws(typeof(InvalidOperationException))]
public void Foo()
{
throw new InvalidOperationException();
}
// After
try
{
Foo();
}
catch (InvalidOperationException invalidOperationException)
{
}
[Throws(typeof(InvalidOperationException))]
public void Foo()
{
throw new InvalidOperationException();
}// Before
Func<int, int> f = x =>
{
try
{
return x;
}
catch (ArgumentException argumentException) { } // redundant
};
// After
Func<int, int> f = x => x;Applies to:
THROW005– Duplicate exception declarationTHROW012– Redundant exception declaration
Removes a [Throws] declaration for an exception type that is never thrown.
// Before
[Throws(typeof(InvalidDataException))] // redundant
public bool Foo2 => true;
// After
public bool Foo2 => true;Applies to:
THROW007– Missing throws from base/interface member
Adds [Throws] attributes so that an overriding or implementing member is compatible with its base or interface contract.
// Before
public interface IFoo
{
[Throws(typeof(InvalidOperationException))]
void Bar();
}
public class Foo : IFoo
{
public void Bar() { throw new InvalidOperationException(); } // THROW007
}
// After
public interface IFoo
{
[Throws(typeof(InvalidOperationException))]
void Bar();
}
public class Foo : IFoo
{
[Throws(typeof(InvalidOperationException))]
public void Bar() { throw new InvalidOperationException(); }
}// Before
public abstract class Base
{
[Throws(typeof(ArgumentException))]
public abstract int Value { get; }
}
public class Derived : Base
{
public override int Value => throw new ArgumentException(); // THROW007
}
// After
public abstract class Base
{
[Throws(typeof(ArgumentException))]
public abstract int Value { get; }
}
public class Derived : Base
{
[Throws(typeof(ArgumentException))]
public override int Value => throw new ArgumentException();
}Applies to:
THROW011– Missing throws from XML documentation
Adds [Throws] attributes based on <exception> tags in XML documentation.
// Before
/// <exception cref="ArgumentException">Thrown when input is invalid.</exception>
public void Baz() // THROW011
{
throw new ArgumentException();
}
// After
/// <exception cref="ArgumentException">Thrown when input is invalid.</exception>
[Throws(typeof(ArgumentException))]
public void Baz()
{
throw new ArgumentException();
}XML documentation comments are placed on the property declaration, not on individual get/set accessors.
The analyzer uses keyword heuristics in the <exception> description to decide which accessor the exception belongs to:
-
If the text mentions “get”, “gets”, “getting”, or “retrieved” →
[Throws]is applied to the getter. -
If the text mentions “set”, “sets”, or “setting” →
[Throws]is applied to the setter. -
If no keywords are found:
- If the property has only a getter or only a setter, the
[Throws]applies there. - If the property has both, the analyzer defaults to the getter.
- If the property has only a getter or only a setter, the
// Before
/// <exception cref="InvalidOperationException">
/// Thrown when getting the value before initialization.
/// </exception>
public int Count // THROW011 (on get accessor)
{
get => throw new InvalidOperationException();
}
// After
/// <exception cref="InvalidOperationException">
/// Thrown when getting the value before initialization.
/// </exception>
public int Count
{
[Throws(typeof(InvalidOperationException))]
get => throw new InvalidOperationException();
}// Before
/// <exception cref="ArgumentException">
/// Thrown when setting a negative value.
/// </exception>
public int Capacity // THROW011 (on set accessor)
{
set
{
if (value < 0)
throw new ArgumentException();
}
}
// After
/// <exception cref="ArgumentException">
/// Thrown when setting a negative value.
/// </exception>
public int Capacity
{
[Throws(typeof(ArgumentException))]
set
{
if (value < 0)
throw new ArgumentException();
}
}// Before
/// <exception cref="IOException">
/// Thrown if the underlying stream is closed.
/// </exception>
public string Data // THROW011 (defaults to get)
{
get => throw new IOException();
set => throw new IOException();
}
// After
/// <exception cref="IOException">
/// Thrown if the underlying stream is closed.
/// </exception>
public string Data
{
[Throws(typeof(IOException))]
get => throw new IOException();
set => throw new IOException();
}Applies to:
THROW017– Deferred enumeration crossing boundary
Adds .ToArray() to deferred sequences that leave the member, forcing materialization so exceptions surface within the current member.
// Before
IEnumerable<string> items = [];
IEnumerable<string> Get()
{
var query = items.Where(x => int.Parse(x) > 0);
return query; // THROW017
}
// After
IEnumerable<string> items = [];
IEnumerable<string> Get()
{
var query = items.Where(x => int.Parse(x) > 0);
return query.ToArray();
}