A high-performance .NET library for defensive argument validation and exception handling. Provides two complementary APIs: Check for performance-critical validation and Throw for detailed exception reporting.
dotnet add package CheckAndThrowCheckAndThrow implements a philosophy that separates concerns when validating inputs and handling errors:
Check.*methods prioritize performance. They validate conditions as efficiently as possible with minimal overhead, making them suitable for hot paths and performance-sensitive code.Throw.*methods prioritize detailed diagnostics. They sacrifice speed for comprehensive error information, generating rich exception messages that help developers diagnose issues quickly.
This design reflects the principle that exceptions should be exceptional. Since exceptions are not expected to be thrown frequently in production code, the extra overhead of detailed error reporting is justified, your code's hot paths stay fast through Check, while debugging becomes effortless through Throw.
Fast argument validation with minimal overhead:
Check.Arg.*— Validate single arguments (null checks, type checks, range validation, collection checks)Check.Args.*— Validate multiple arguments togetherCheck.State.*— Validate application state conditionsCheck.Expression.*— Evaluate expressions for state validation
Perfect for:
- Entry points of hot methods
- Loops and frequently-called code
- Performance-critical paths
Detailed exception reporting for when things go wrong:
Throw.Arg.*— Throw rich ArgumentExceptions with contextThrow.State.*— Throw InvalidOperationExceptions with diagnostic infoThrow.Unreachable(),Throw.NotImplemented(),Throw.NotSupported()— Handle unreachable code paths
Perfect for:
- Helper methods and validation logic
- Clear separation of error reporting from validation
- Comprehensive debugging information in exceptions
Key insight: Check methods call Throw methods underneath when validation fails.
This means:
- Success path → Fast:
Checkvalidates with minimal overhead - Failure path → Detailed:
Checkdelegates toThrowfor rich error reporting
// Check validates efficiently; throws only when validation fails
public void ProcessData(int[] data)
{
Check.Arg.NotNull(data); // Returns immediately if valid
// ... process data ...
}
// You can also use Throw directly for explicit error handling
public User GetUser(UserType userType)
{
return userType switch
{
UserType.Admin => GetAdmin(),
UserType.Customer => GetCustomer(),
// Generic return type allows usage in switch expressions
_ => Throw.Arg.InvalidEnumValue<User>(typeof(UserType), userType)
};
}Why this design? Since exceptions interrupt execution and are handled at a higher level anyway, the performance cost of detailed error messages is negligible. This architecture ensures:
- Your application remains responsive in the common case (success)
- Rich debugging information available in the exceptional case (failure)
- Clean, readable validation code everywhere
Is prefixes were removed from guard and throw helpers (for example, Check.Arg.IsNotNullOrEmpty is now Check.Arg.NotNullOrEmpty, and Throw.Arg.IsNull is now Throw.Arg.Null). This is a source and binary breaking change: rename calls and recompile consumers. Compatibility aliases are not provided.
Add CheckAndThrow to your project via NuGet:
dotnet add package CheckAndThrowThe package includes Roslyn Quick Actions by default. Place the caret on a supported parameter and open your IDE's Quick Actions menu (for example, Ctrl+. in Visual Studio or Alt+Enter in Rider). Choose Add Check.Arg.NotNull guard for a reference parameter, expand Add string guard clause for a string parameter, or expand Add range guard clause for a numeric parameter.
The null action supports block-bodied methods and constructors whose parameters are non-nullable reference types or unannotated generic type parameters. It does not appear for explicitly nullable parameters such as string? or T?, value types, out parameters, expression-bodied members, lambdas, local functions, or primary constructors. It inserts a single global::CheckAndThrow.Check.Arg.NotNull(parameter); statement and does not report build warnings.
For non-nullable string parameters, Add string guard clause offers Check.Arg.NotNullOrEmpty and Check.Arg.NotNullOrWhiteSpace. If an individual Check.Arg.NotNull already guards the parameter, the selected action replaces it while preserving its use and arguments. Either string guard suppresses the individual null action and is excluded from Add Check.Args.NotNull guard; the bulk action remains available when 2–16 other eligible parameters remain.
For declarations with 2–16 eligible parameters, Add Check.Args.NotNull guard adds one global::CheckAndThrow.Check.Args.NotNull(...) guard for all eligible parameters. It safely converts replaceable top-level Check.Arg.NotNull and Check.Args.NotNull guards; when a prior individual guard's return value was assigned or declared, its value is preserved through the returned tuple. Explicitly nullable and other excluded parameters are not included. The bulk action is unavailable when fewer than two or more than 16 parameters are eligible, or when an existing guard has an unsupported result-consuming shape.
Disable all bundled CheckAndThrow IDE actions for a project or repository without removing the package:
<PropertyGroup>
<CheckAndThrowEnableAnalyzers>false</CheckAndThrowEnableAnalyzers>
</PropertyGroup>For numeric parameters, Add range guard clause is hidden by default and offers existing Check.Arg sign checks: reject negative (ZeroOrPositive), reject negative and zero (Positive), reject positive (ZeroOrNegative), or reject positive and zero (Negative). For floating-point and generic numeric types, it also offers the corresponding infinity-permitting checks when the referenced CheckAndThrow API supports them. Existing Negative, Positive, ZeroOrNegative, ZeroOrPositive, infinity, and InRange guards suppress the action. The action supports methods and constructors with executable bodies, inserts a top-level guard when needed, and uses the guard's returned value for a safe direct first use.
Set different default severities for public and non-public members in .editorconfig, then override individual analyzers by diagnostic ID:
[*.cs]
checkandthrow_analyzers.public_severity = warning
checkandthrow_analyzers.non_public_severity = silent
dotnet_diagnostic.CAT0001.public_severity = error
dotnet_diagnostic.CAT0001.non_public_severity = noneThe same dotnet_diagnostic.CAT000x.public_severity and .non_public_severity keys work for CAT0001 (individual null guard), CAT0002 (bulk null guard), CAT0003 (range guard), and CAT0004 (string guard). Per-ID visibility settings override the corresponding all-analyzer visibility setting. Without either, diagnostics remain hidden. Values: none (disable), silent or hint (hidden), suggestion (info), warning, error. Missing or invalid values fall back to the next setting. These settings apply to methods, constructors, and writable properties: only members declared public in publicly accessible types count as public; protected members and public members inside internal types count as non-public. Properties use property visibility, including properties with private setters.
Standard dotnet_diagnostic.CAT000x.severity still applies across both visibilities, including none to disable an analyzer entirely. Roslyn analyzer/code-fix support must be enabled in your IDE; supported hosts include Visual Studio, JetBrains Rider, and VS Code with the Microsoft C# extension.
- DotNet standard 2.0
- DotNet standard 2.1
- DotNet 8.0
- DotNet 10.0
Contributions are welcome! Please:
- Open an issue to discuss your idea
- Submit pull requests with clear descriptions and appropriate tests
- Ensure all public members have XML documentation comments
Licensed under the MIT License. See LICENSE file for details.