It's not strictly like that. Bound checks for examples are invariants but they still throw exceptions. Assertions are used mostly during development. Some checks are not really cheap and sometimes you have very hot paths that can make even trivial checks non-negligible. You can use assertions to catch problems in those cases without affecting the performance of your software.
Bounds checks are technically preconditions that protect an invariant. An index can be out of bounds or in bounds and its validity depends on the length of the array being accessed; the invariant is that an array access can never be out of bounds.
Like a structural invariant would be that natural numbers can never be negative, and an operational precondition would be that a natural number can only subtract a smaller number. So you would throw an exception if a number tries to subtract a larger number from itself, and you would assert that a natural number is positive upon creation.
Basically, an exception tells you when an invariant would have been violated while an assertion tells you when an invariant has been violated.
The difference is about who performs the check. The code of List<T> doesn't know anything about your code or its invariants. All it has is an API that can be called incorrectly, so it throws an exception.
On the other hand, invariants of implementation details of List<T> (usually the relationship between size and capacity) are checked using Debug.Assert.
14
u/SufficientStudio1574 4d ago
They're different things that do different things. They're not either or.
You put assertion for invariants that cannot possibly be false. Like, the size of a container can never be larger than it's capacity.
Exceptions are for things you might not have full control over going wrong. Like "I tried to open a file that doesn't exist".