In this article, we discuss how to create guard clauses, how they differ from user input validation, and how to improve our guards by writing clean guard clauses leveraging the GuardClauses library.
Let’s dive in!
What are Guard Clauses? – A Simple Example
A guard clause ensures that parameters meet certain conditions. If conditions are not met the executing method is exited either using a return
statement or, most commonly, throwing an Exception
. Unlike user input validation, where we expect wrong input, guard clauses prevent unexpected behavior from occurring when we instantiate domain objects.
Leveraging guard clauses as early as possible will enable us to short-circuit the method before any system resources are wasted performing unneeded operations.
Some common guard clauses check if an object
we pass is null
, a string
parameter is empty or whitespace, and an integer
value is negative or zero.
Let’s illustrate this with a simple example:
public class Car { public Car(Engine engine) { if (engine == null) { throw new ArgumentNullException(nameof(engine), "Engline cannot be null"); } Engine = engine; } public Engine Engine { get; private set; } }
Here, we create a simple Car
class that has one property of Engine
type, we also pass the engine
as a parameter to the constructor. A car without an engine is no car at all so we check if the engine
parameter is null
, and if it is – we throw an ArgumentNullException
. This way we make sure that the Car
objects we create will always have a valid Engine
.
Next, we define our Engine
:
public class Engine { public Engine(int horsePower, int cylinders, string cylinderLayout, int topSpeed, FuelType fuelType) { if (horsePower < 0) { throw new ArgumentException("Value cannot not be negative", nameof(horsePower)); } if (cylinders <= 0) { throw new ArgumentException("Value cannot be negative or zero", nameof(cylinders)); } if (string.IsNullOrWhiteSpace(cylinderLayout)) { throw new ArgumentException("Value cannot be null or whitespace.", nameof(cylinderLayout)); } if (topSpeed == 0) { throw new ArgumentException("Value cannot be zero", nameof(topSpeed)); } if (fuelType != FuelType.Diesel || fuelType != FuelType.Petrol) { throw new ArgumentException("Fuel type must be either diesel or petrol", nameof(fuelType)); } HorsePower = horsePower; Cylinders = cylinders; CylinderLayout = cylinderLayout; TopSpeed = topSpeed; FuelType = fuelType; } public int HorsePower { get; } public int Cylinders { get; } public string CylinderLayout { get; } public int TopSpeed { get; } public FuelType FuelType { get; } }
In a new file, we create the definition of our Engine
class – it has five properties. First, we have HorsePower
, Cylinders,
and TopSpeed
of integer
type. Then we continue with CylinderLayout
of string
type. Finally, we have a FuelType
property of FuelType
type, which is a simple enumeration containing two values – Petrol
and Diesel
. We pass the values of those properties to the constructor when creating an object of Engine
type.
We also have five if
checks that throw an ArgumentException
with a custom message when any of our passed parameters doesn’t meet a particular condition. This is a good way of making sure that our objects always contain valid data and behave as expected.
A downside to the common if
approach is that for objects with many properties we risk overly long sections of guard clauses which reduce our code readability.
Let’s see how we can improve that.
Install GuardClauses Package
GuardClauses
is a NuGet package developed to make guard clauses more readable and easy to understand. It also comes with various pre-defined clauses. Among them are guards against Null
, NullOrWhiteSpace
, and OutOfRange
, to name a few.
Let’s install the GuardClauses
package through .NET CLI:
dotnet add package Ardalis.GuardClauses
How to Use Pre-defined Guard Clauses?
Now that we have the library in our project, let’s make use of its pre-defined clauses:
public class Car { public Car(Engine engine) { Engine = Guard.Against.Null(engine); } public Engine Engine { get; private set; } }
In our Car
class, we will remove the null
check and use the Guard.Against.Null
method we get from the Ardalis.GuardClauses
namespace (don’t forget to add it to your using). To instantiate the Car
class we pass it the engine
parameter that we receive in the constructor. If engine
is null
we will receive an ArgumentNullException
. If engine
passes the null check its value is assigned to our Engine
property.
Let’s pass a null
value to our constructor and check the result:
Value cannot be null. (Parameter 'engine')
We get an Exception
with a default message that gives us valuable information, but we can improve it:
public Car(Engine engine) { Engine = Guard.Against.Null(engine, nameof(engine), "Engine cannot be null!"); }
Using an overload of the Null
method, we pass the engine
parameter, its name using nameof
, and a custom message.
Let’s pass another null
and check the result:
Engine cannot be null! (Parameter 'engine')
We can see that we get the message we defined, neat!
This is already an improvement, but let’s see what it does with larger guard clause blocks:
public class Engine { public Engine(int horsePower, int cylinders, string cylinderLayout, int topSpeed, FuelType fuelType) { HorsePower = Guard.Against.Negative(horsePower); Cylinders = Guard.Against.NegativeOrZero(cylinders); CylinderLayout = Guard.Against.NullOrWhiteSpace(cylinderLayout); TopSpeed = Guard.Against.Zero(topSpeed); FuelType = Guard.Against.EnumOutOfRange(fuelType); } public int HorsePower { get; } public int Cylinders { get; } public string CylinderLayout { get; } public int TopSpeed { get; } public FuelType FuelType { get; } }
We replace our guard clauses in the constructor of the Engine
class with the pre-defined ones from the GuardClauses
package. The Negative
method will guard for any value below 0.
NegativeOrZero
will only accept positive numbers. Zero
will throw and Еxception
only if the parameter is 0
. NullOrWhiteSpace
will guard us against null
, empty, or white space string
values. EnumOutOfRange
will prevent us from passing an invalid enum
value to our constructor.
Updating our Car
class had made a minor improvement, but here we see the awesome power of the package – we were able to remove over 20 lines of code!
But there is more we can do with this package.
How To Create Custom Guard Clauses?
When designing the GuardClauses library, Steve Smith provided us with the opportunity to create our custom guard clauses:
public static class PetrolEngineGuard { public static FuelType PetrolEngine(this IGuardClause guardClause, FuelType fuelType) { if (fuelType == FuelType.Petrol) { throw new ArgumentException("Fuel type cannot be petrol!", nameof(fuelType)); } return fuelType; } }
To guard against Petrol
-fueled engines, we create a static
PetrolEngineGuard
class that has one static
PetrolEngine
method with a return type of FuelType
. The method takes in an IGuardClause
preceded by the this
keyword, which is needed for creating extension methods, and a fuelType
parameter of FuelType
type.
The custom guard clause we create will throw an ArgumentException
with the message Fuel type cannot be petrol! if the value of the fuelType
parameter is Petrol
. Otherwise, we return its value. Make sure you have the PetrolEngineGuard
class in the Ardalis.GuardClauses
namespace otherwise the extension method might not work.
Now, let’s use our clause:
public Engine(int horsePower, int cylinders, string cylinderLayout, int topSpeed, FuelType fuelType) { HorsePower = Guard.Against.Negative(horsePower); Cylinders = Guard.Against.NegativeOrZero(cylinders); CylinderLayout = Guard.Against.NullOrWhiteSpace(cylinderLayout); TopSpeed = Guard.Against.Zero(topSpeed); FuelType = Guard.Against.PetrolEngine(fuelType); }
In the constructor of our Engine
class, we replace the Guard.Against.EnumOutOfRange
method with Guard.Against.PetrolEngine
and that’s it – clean and simple.
Let’s pass Petrol
as FuelType
and examine the result:
Fuel type cannot be petrol! (Parameter 'fuelType')
We receive an expected ArgumentException
that the “Fuel type cannot be petrol”.
Conclusion
In this article, we have learned what guard clauses are and how to use them. More importantly, we have learned about the GuardClauses
package. It provides us the capability of writing clean guards both with pre-defined and user-defined clauses – a valuable thing that improves our code readability.