>- How to guard arguments and assert invariants with the Contract family in SnowBank.Core (namespace NotNullOrWhiteSpace / Positive / GreaterThan / GreaterOrEqual / LessThan / LessOrEqual / EqualTo / NotEqualTo / ValueNotNull) that validate a caller's arguments and throw ArgumentNullException / ArgumentException / ArgumentOutOfRangeException, versus the condition assertions (Contract.Requires / Assert / Ensures / Invariant / Fail) that check internal invariants and throw ContractException, plus the three compile levels (always-on Contract.X, Debug-only Contract.Debug.X, Paranoid.X under PARANOID_ANDROID), the CallerArgumentExpression auto-message, the nullable-flow and StackTraceHidden behavior, and the NUnit test integration. Use whenever code validates a method argument, replaces a hand-written `if (x == null) throw new ArgumentNullException(...)` or `Debug.Assert(...)` or `ArgumentNullException.ThrowIfNull(...)`, adds a precondition or state invariant, chooses between Contract, Contract.Debug and Paranoid, or hits a ContractException. Prefer these over raw throws and BCL asserts in SnowBank.Core / FoundationDB.Client code.
npx skills add https://github.com/SnowBankSDK/foundationdb-dotnet-client --skill snowbank-contracts
Contract is the guard and assertion family used across SnowBank.Core and FoundationDB.Client. It
replaces raw throw new ArgumentNullException(...), ArgumentNullException.ThrowIfNull(...),
ArgumentOutOfRangeException.ThrowIf*, and System.Diagnostics.Debug.Assert(...). The namespace is
SnowBank.Diagnostics.Contracts; it is a global using in the SnowBank projects, add
using SnowBank.Diagnostics.Contracts; in a consumer.
There are two families, and they are not interchangeable:
Contract.NotNull, Positive, GreaterThan, ...) validate arguments thatcome from OUTSIDE the method. They are the logical equivalent of
if (cond) throw new Argument...Exception(...). A failure means the CALLER passed a bad argument.
Always on (Debug and Release). Each throws the matching Argument* exception.
Contract.Requires, Assert, Ensures, Invariant, Fail) check acondition that must NEVER be false, an invariant of your own code. A failure means the component
itself has a bug. Each throws ContractException.
The rule for choosing: ask who can trigger it. A caller outside the type gets an argument guard. Your
own code breaking its own invariant gets Contract.Debug.Requires for a dev-time check, or an
explicit if (...) throw when the invariant must hold at run time (section 3).
Use these to validate public-API arguments. Each captures the argument name on its own (see section 5),
takes an optional message as the second argument, and throws the exception below.
| Guard | Replaces | Throws |
|---|---|---|
| Contract.NotNull(x) | if (x == null) throw new ArgumentNullException(...) | ArgumentNullException |
| Contract.NotNullOrEmpty(s) (string) | null + s.Length == 0 | ArgumentNullException (null) / ArgumentException (empty) |
| Contract.NotNullOrWhiteSpace(s) | string.IsNullOrWhiteSpace(s) | ArgumentNullException (null) / ArgumentException (blank) |
| Contract.NotNullOrEmpty(collection) | null + empty | ArgumentNullException / ArgumentException |
| Contract.NotEmpty(collection) | .Count == 0 | ArgumentException |
| Contract.Positive(n) | if (n <= 0) throw ... | ArgumentException |
| Contract.PowerOfTwo(n) | bit check | ArgumentException |
| Contract.GreaterThan(n, t) / GreaterOrEqual / LessThan / LessOrEqual | compare + throw | ArgumentOutOfRangeException |
| Contract.EqualTo(x, v) / NotEqualTo(x, v) | compare + throw | ArgumentException |
| Contract.ValueNotNull(x) | null check that returns x | ArgumentNullException |
| Contract.PointerNotNull(p) (unsafe) | p == null | ArgumentNullException |
public void Load(Root root, int count)
{
Contract.NotNull(root);
Contract.Positive(count);
// root is non-null from here (section 5)
}
ValueNotNull returns its argument, for a single-line setter:
public string Name
{
get => this.name;
set => this.name = Contract.ValueNotNull(value, "Name cannot be null");
}
Numeric guards exist for int, long, double, float (and uint / ulong for PowerOfTwo).
NotNullOrEmpty / NotEmpty have overloads for arrays, collections, Slice, and ArraySegment<T>.
NotNullAllowStructs<T> is [Obsolete]; call NotNull.
Non-trivial check: when the check is heavy (for example a SequenceCompareTo bound check at a
public entry point), write a plain if (...) throw with a message, not a Contract call.
Use these for a condition that must never be false. They throw ContractException.
| Method | Meaning |
|---|---|
| Contract.Requires(cond) | precondition at the start of a method |
| Contract.Assert(cond) | assertion inside a method body |
| Contract.Ensures(cond) | postcondition at the end |
| Contract.Invariant(cond) | an invariant that must always hold |
| Contract.Fail(message, ex?) | fail unconditionally (returns never) |
Do NOT use Contract.Requires to validate a public argument. It throws ContractException, which
signals an internal bug, not a caller error. Public arguments get the typed guards in section 1, which
throw the correct Argument* exception.
For an internal invariant, the common form is the Debug variant Contract.Debug.Requires (section 3),
not the always-on Contract.Requires. See section 3 for when to use an explicit if (...) throw
instead.
The same method set exists at three compile levels. Pick by how much you are willing to pay at run
time.
| Level | Compiled | Use for |
|---|---|---|
| Contract.X(...) | always (Debug and Release) | the argument guards (section 1) |
| Contract.Debug.X(...) | Debug only ([Conditional("DEBUG")], removed from the Release binary) | state invariants inside private/internal methods |
| Paranoid.X(...) | only when the PARANOID_ANDROID symbol is defined | the hottest paths, where even a Debug check is too costly |
Paranoid.IsParanoid is a runtime flag for code that wants to skip expensive setup when Paranoid
checks are off.
Default for a state invariant: Contract.Debug.Requires. It is the equivalent of Debug.Assert:
it catches a regression during day-to-day development (Debug) and costs no CPU in Release, where the
public boundary already validated. A failure means a bug in the component itself, not a caller error.
private void Apply(Node node, int position)
{
Contract.Debug.Requires(node is not null && position >= 0); // state invariant, Debug only
// ...
}
An invariant that must hold at run time gets an explicit if (...) throw, not Contract.Requires.
A condition worth checking in Release is important enough to deserve an explicit check and a meaningful
exception. The always-on Contract.Requires / Assert exist, but are rarely the right tool: use
Contract.Debug.Requires for a dev-time check, or if (...) throw for a real runtime invariant.
Every guard and assertion carries [CallerArgumentExpression], so the compiler puts the source text
in the message. Do not write a message just to name the argument or restate the condition.
Contract.NotNull(root) puts the name root in the ArgumentNullException.Contract.Debug.Requires(node is not null && position >= 0) puts the literal condition"node is not null && position >= 0" in the ContractException.
The optional message argument is for extra context only: Contract.NotNull(root, "Root is required").
[NotNull] and [DoesNotReturnIf(false)], and the JetBrains[AssertionMethod] / [AssertionCondition] attributes. After Contract.NotNull(x), both the C#
compiler and ReSharper treat x as non-null for the rest of the method, which removes
"possible null dereference" (CS8602) false positives.
[StackTraceHidden], so the guard frame does not appear in thestack trace. The trace points at the caller.
AggressiveInlining; a value-type argument to NotNull is optimized away with noboxing.
Under a unit-test runner, a Contract failure becomes a test assertion, and debugger breakpoints are
muted so an unattended CI run does not block. Contract.IsUnitTesting is set true when the runner is
detected.
Only NUnit is detected today (the failure maps to NUnit.Framework.AssertionException). xUnit,
MSTest, and TUnit each throw their own assertion type and are not yet mapped.
ArgumentNullException.ThrowIfNull does not exist on .NET Framework, and a static extension methodcannot shim it, so it is not portable to the netstandard 2.0 / net472 consumers.
Contract changes mode by environment: it breaks into the debugger when one is attached, and raisesa formatted assertion under a test runner (section 6).
[CallerArgumentExpression] removes the boilerplate message (section 4).Contract. prefix aligns a stack of parameter checks at the top of a method.(Historical: the helpers once aided JIT inlining, because a new FooException in the body blocked it.
The modern JIT handles this, so do not cite inlining as a reason today.)
Contract.NotNull / Positive / ...), which throws thematching Argument* exception. Never Contract.Requires for that: it throws ContractException, the
wrong signal.
Contract.Debug.Requires / Debug.Assert insideprivate/internal methods. Debug only, the equivalent of Debug.Assert: catches regressions in
development, zero Release cost.
if (...) throw, not the always-onContract.Requires. A condition worth checking in Release deserves an explicit check and a
meaningful exception.
System.Diagnostics.Debug.Assert(...) in this codebase; use Contract.Debug.Assert(...).ArgumentNullException.ThrowIfNull(...) or ArgumentOutOfRangeException.ThrowIf*; use theguards (portability, section 7).
if (...) throw, not a Contract call dressed over it.Positive, EqualTo, NotEqualTo, and the empty-string /empty-collection cases throw ArgumentException; GreaterThan / GreaterOrEqual / LessThan /
LessOrEqual throw ArgumentOutOfRangeException; the null cases throw ArgumentNullException.
Take snowbanksdk/snowbank-contracts from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.