Code assertions is a set of helper methods aimed to proof your assumptions at runtime.
Every time you want to be sure your code is working right.
Argument validation, checking the state of the object, health checks on debug builds? Here we go:)
Perfectly. No trace under normal conditions and you will be notified if things go wrong.
To be more precise: for each assertion failure you will be awarded with an exception but wait, there's more! If you attach the debugger the execution will break exactly on the line with the failed assertion, not inside the assertion method itself. And it does not matter if the exception is swallowed somewhere down the call stack.
I hope that way you will be annoyed enough to fix the error instead of just ignoring it. Don't thank for this.
We fight for it.
First, we're running perftests to proof that all assertions are running not slower than usual if-then-throw checks.
Second, you can mark assertions as debug-time only and they will be stripped out from release builds. More on that later.
Both yes and no.
No, there's no extensibility points. All in all, it's simple assertions class, no magic attached (almost, more on that later).
And yes, you can easily create your own sets of assertions and have the same experience the out-of-the box assertions provide.
All you have to do is read the instruction. In fact, you are.
You will be disappointed, I promise!
Code.NotNull(argument, nameof(argument));
I'm afraid that's all. Seriously, if you want to use the assertions - what are you doing here?:) Go away and write the code and use the autocomplete to learn. This is how it is assumed to work.
There are three main parts of the code assertions:
- Exception factory methods. You need them to throw out in case nothing else helps.
- Implementations of common assertions.
- And, finally, helper methods to write your own assertions inline with a user code.
Let's take it one-by-one:
These are meant to be used in cases you want to throw the assertion exception explicitly, say, from default switch clause:
switch (someEnum)
{
...
case default:
throw CodeExceptions.UnexpectedValue(someEnum); // InvalidOperationException
}
Here they are:
public static SomeData CountStatistics(string content, int start, int count)
{
Code.NotNull(content, nameof(content)); // ArgumentNullException
Code.IsBetween(start, nameof(start), 0, content.Length - 1); // ArgumentOutOfRangeException
Code.IsBetween(count, nameof(count), 0, content.Length - start); // ArgumentOutOfRangeException
...
// here be boring code
}
Pretty simple, right? Actually, it's even simpler than this:
public static SomeData CountStatistics(string content, int start, int count)
{
Code.NotNull(content, nameof(content)); // ArgumentNullException
Code.ValidIndex(start, nameof(start), 0, content.Length); // IndexOutOfRangeException
Code.VaildCount(count, nameof(count), start, start.Length); // ArgumentOutOfRangeException
...
// here be boring code
}
And so on.
I'm not going to document all of them as their usage should be obvious. And if it is not,
the mission is failed already, so there's no point in topping the bad code with bad documentation.
Thank you for caring.
These should be used in case you did not find proper assertion method to call but do not want to copy-paste boilerplate like this:
if (!IsGood(arg)) // inverted assertion
{
throw CodeExceptions.Argument(
nameof(arg),
"You're bad one, {0}.",
argument);
}
Instead, use the
Code.AssertArgument( // ArgumentException
IsGood(arg), // assertion
nameof(arg), // name of argument
"You're bad one, {0}.", // message format
arg); // format args
for assertions on argument
and the
Code.AssertState( // InvalidOperationException
SomeCheck(this.Location), // assertion
"We're not in Kansas anymore!"); // guess what
for state-checking assertions.
As you may have noticed, the assertion helpers differs from assertion implementation methods.
First, they allow you to specify detailed message (or message format string).
Second, they are named after exception being thrown, not after assertion they're implementing (they are not).
As a rule of thumb, assertion helpers' names are prefixed with Assert
but there're exceptions. To name one,
Code.DisposedIf(disposedFlag, this, "Some message");
These are not meant to be used directly. Instead, wrap the helper into a new method alike
protected void AssertNotDisposed() =>
Code.DisposedIf(_disposed, this, "Some message");
and copy-paste the AssertNotDisposed();
line across the code.
Assertion helper methods are a simple and easy to use.
However, there's a performance hit if you're using the overload with string messageFormat, params object[] args
.
Each time these overloads are called a new array will be created and filled with the args passed into a method call.
If the assertion assumed to be called hundred times per second or more, consider to use assertion implementation method or to write your own assertion method, that's easy:
private static void AssertIsGood(int arg, string argName)
{
if (!IsGood(arg)) // inverted assertion
{
throw CodeExceptions.Argument(
nameof(arg),
"You're bad one, {0}.",
argument);
}
}
Things are getting much worse if you're using assertion helper methods together with the c# 6 string interpolation feature, like this:
Code.AssertArgument(
IsGood(arg), nameof(arg),
$"You're bad one, {arg}.");
Now the entire string has to be formatted on each method call as the compiler converts the code into
Code.AssertArgument(
IsGood(arg), nameof(arg),
string.Format("You're bad one, {0}.", arg));
Note that if assertion is not fired, the formatted string would not be used at all.
In fact, all it does is adding more pressure for the GC. Not a good thing to happen.
This issue could not be solved with the addition of overload with FormattableString
parameter,
the compiler will ignore it.
You can't have enough performance. So, here goes my favorite trick for code speedup:
there is no code faster that the one that does not execute.
If you have tight loop with a lot of assertions or the assertion itself is very complex and heavy
(e.g. checks that file exists or something like this) it may be a good idea to add these assertions only into debug builds.
To achieve this, use methods from DebugCode
class instead of the ones from Code
. As an example:
private void DoSomething()
{
// Debug-only assertions;
DebugCode.AssertState(File.Exists("someFile"), "File is gone.");
DebugCode.AssertState(ConnectionOk(connectionString), "We're doomed!");
// This one will be checked in release builds too;
Code.AssertState(IsInitialized, "Setup me!");
...
}
The DebugCode
class contains counterparts for almost all methods from the Code
class.
The ones that missing are assertions meant to be thrown always or not thrown at all. Code.DisposedIf
, for example.
IMPORTANT Note that the check is removed only if it is written inline, as an argument of the assertion. For example, here
var allOK = File.Exists("someFile") && ConnectionOk(connectionString);
DebugCode.AssertState(allOK, "You lose.");
the allOK
line will be executed in release builds too, only line with the assertion will be stripped out
If you do not want to bother with all this stuff, just wrap the assertions into a new method and mark it with a
[Conditional(DebugCode.DebugCondition)]
attribute. Here's an example:
[Conditional(DebugCode.DebugCondition)]
private void ValidateMe()
{
// The method calls will performed only in debug builds;
Code.AssertState(File.Exists("someFile"), "File is gone.");
Code.AssertState(ConnectionOk(connectionString), "We're doomed!");
Code.AssertState(IsInitialized, "Setup me!");
}
private void DoSomething()
{
// Debug-only assertions;
ValidateMe();
...
}
The debug-only assertion method can be static or instance one, usual restrictions from ConditionalAttribute apply.
TBD
TBD