TL;DR - no, no, no!
Ok, this was a bit blunt, so why shouldn’t you use nullable bools?
Let’s start from the beginning …
A boolean in .NET is a struct hence is a value type and can not be null. By definition a bool value is either true
or false
; binary the basic building blocks of computing.
Code when written should be clear and through how it is constructed should show intent, I do not believe a Nullable<bool>
(or bool?
) is clear. If you require mechanism for 3 or more states then use an enum
.
When could this be strange? Well let’s take the example of a web api end point. We want to pass in a boolean value into the action method and process as expected. Using a nullable bool parameter indicates either it’s not been specified, it’s true or it’s false.
public IActionResult Nullable(bool? value)
{
if (value == null)
{
return Ok("Null value");
}
if (value.Value)
{
return Ok("True");
}
return BadRequest("False");
}
This is not ideal, using a bool as a tri state option isn’t clear to a consumer of the api but it’s also not extensible. Using an enum would be clearer. If you are writing a public api you need to make sure that it is clear and extensible otherwise you end up in a backwards compatibility nightmare.
public enum TriState
{
None = 0,
Positive = 1,
Negative = 2
}
It opens up extensibility for the future, can be bound by name or integer representation, and the default if not provided is 0
aka a valid state.
public IActionResult TriState(TriState value)
{
switch (value)
{
case Controllers.TriState.None:
return Ok("'Null' value");
case Controllers.TriState.Positive:
return Ok("True");
}
return BadRequest("False");
}
So what happens if the end point understands that null
means false
and has the same flow through the code? Well we’re getting back to the proper binary nature of the parameter, but the caller still does not see the intent and makes the code a little cluttered and it’s still not right.
public IActionResult Nullable2(bool? value)
{
if (value ?? false)
{
return Ok("True");
}
return BadRequest("False");
}
This is where optional parameters come in. The model binders of aspnet core can handle optional parameters as regular methods in pure C# does. We can now specify that the parameter should be the default(bool)
which is false
.
public IActionResult Default(bool value = default(bool))
{
if (value)
{
return Ok("True");
}
return BadRequest("False");
}
This version makes the intent of the method clear, the code is clean and it flows as we want/expect it to.
Web api model binding is fine, but what about if I add a new column to a database table and want it to be null? I would ask you “what are you trying to represent in this column? Is null valid?”. If it’s a multi state value then use an integer to present an enum. If it’s a binary state then default the value to false.
As I previously said in an earlier post; the end game of a majority of code after all is adding functionality but allowing for maintainability which means clean and readable code. Don’t over complicate code but K.I.S.S!
I’d be interested in hearing your thoughts. Please contact me on Twitter @WestDiscGolf