How to assert assumptions in code that might not be true?

11/18/2024
3 minute read

Sometimes we have to make assumptions in our code. For example, that a certain property or variable has a specific value. On top comes, we have a specific idea but we are not 100% sure if they are correct. Let's meet Debug.Assert.

Disclaimer

I will start with a disclaimer, before I receive salted emails and comments. I know there is TDD where you can write down your assumptions as test. This article is not about that, sometimes you have assumptions about certain characteristics of the data or the environment that you don't want to test, because for whatever reason (for example because you think the variable should have never ever that value in this code path). So you can see this as an additional tool in your toolbox during development time.

Debug.Assert

Now with that out of the way, let's imagine the following code:

return view
    .Where(s => s.TimeSignal is not null)
    .GroupBy(v => v.SomeField)
    .Select(v =>
    {
        var signals = v.ToList();
        Debug.Assert(signals.Count == 2);
        return new Model
        {
            Id = v.Key,
            First = CreateFromSignal(signals[0].TimeSignal),
            Last = CreateFromSignal(signals[1].TimeSignal),
        };
    })
    .ToArray();

The code parses a collection of view objects, groups them by SomeField and then creates a new Model object for each group. The basic premise here is that each group should have exactly two elements. If that's not the case, then something is wrong with the data and we want to know about it. And sure we can write a test for that, but does that mean that the production/test database will also have that assumption? Maybe, maybe not.

The cool thing about Debug.Assert is, as the name suggest, it only runs in debug mode. So you can sprinkle your code with these assertions and they will only run in debug mode (hopefully your code on DEV/UAT/PROD or however you call it runs in RELEASE mode). If the condition is not met, then you get an exception.

I like that approach to write down some of my assumptions in the code. It's like a little note to myself and my colleagues. And if we know for sure, that the assumption should hold true in 100% of cases, well then either we assert this in one way or another or just remove the Debug.Assert and let the code run. Often times, I run my code locally with DEBUG mode enabled.

Microsoft itself is using that technique all over the place: https://grep.app/search?current=2&q=Debug.Assert&case=true&filter[lang][0]=C%23

Trace.Assert

There is also Trace.Assert which is similar but runs if the TRACE symbol is defined.

Debug Release

This picture is from Rider and my project properties of the executing project. As we can see, TRACE is defined in DEBUG and RELEASE mode. So Trace.Assert would run in both modes. If you remove the constant or create a new configuration without TRACE, then Trace.Assert would not run.

DebuggerTypeProxy - Displaying complex states in the debugger

With the DebuggerTypeProxy we have the possibility to show more complex states. This can be helpful in certain cases where an object for example has a lot of properties.

Mutable value types are evil! Sort of...

You might have heard that mutable value types are evil. But why is that and why does the .NET framework use them then? Are they really that evil?

Let's have a look at a few examples and have a look what is going on!

RequiredIf - Extend the validation in Blazor

Just imagine you have your beautiful model in a form. And Blazor is very kind and does validation for you. It is really straight forward because you can annotate your model with some attributes like RequiredAttribute to tell Blazor: "Hey this property has to be set, otherwise the form will not be submitted."

But what if you want to have that easy setup with attributes but also want to say to Blazor: "Hey this property has to be set, if the other property is true.* Well, that does not work out of the box. So let's make it together!

An error has occurred. This application may no longer respond until reloaded. Reload x