Resolving CA1062: Validate arguments of public methods
What's the problem?
If you're using Code Analysis to ensure that your code satisfies Microsoft's Framework Design Guidelines, you've probably encountered warning CA1062: Validate arguments of public methods. It occurs when you use a parameter of a public method in a way that could cause an unexpected NullReferenceException to occcur.
You can use the methods in Existential.Net to resolve that warning consistently and concisely, without having to sprinkle your code with lots of lines of repetitive null-checking.
How do I resolve it?
There are three steps.
1. Set up .editorconfig (once per solution)
There are all sorts of other good reasons to set up a .editorconfig file if you don't have one already; you can read about those elsewhere.
For our purposes, there's one line you need to add to an .editorconfig. It looks like this:
dotnet_code_quality.null_check_validation_methods = ThrowIfNull|ThrowIfNullOrEmpty|ThrowIfNullOrWhiteSpace|ThrowIfNullOrEmpty``1(``0,System.String)
It lists methods in Existential.Net that Code Analysis should recognise as being valid null-check validation methods. In the unlikely event that you need to specify their names more fully to avoid ambiguity, you can do so in ID string format.
You can read more about this in the documentation for the null_check_validation_methods setting.
This version of the .editorconfig line replaces a previous one that included additional methods related to the use of Maybe<T> described below; those methods are still available, but in fact if used correctly don't need to (and shouldn't) be identified as null check validation methods. This is because they don't guarantee that the value they're passed is null; instead they wrap it in a way that makes it safe to handle - only through the wrapper! - whether the value is null or not. The original variable remains unsafe to use unless null checked in another way.
2. Pick the approach you prefer
Existential.Net supports two alternate approaches to dealing with nulls, represented by the classes Validate and Maybe<T>. The static Validate class contains methods that will throw an ArgumentNullException if a null is encountered. They're simple to understand, ensure consistent handling of null-detection, and save a few lines of code each time they're used, but of course they don't eradicate exceptions, they just make them a bit more consistent and informative; and you'll still need to decide for yourself how to handle them.
Maybe<T> takes a little more getting used to, but it may be the better option as it doesn't require configuration in .editorconfig and doesn't throw an exception. It's similar in concept to Nullable<T> - it may or may not contain a value - but takes the approach a little further. Take a little time to read Should I Use Maybe<T>? to see if it's for you.
3. Call a relevant method
If you've chosen Validate:
Using one of the following null-checking methods on the Validate class will resolve CA1062:
- Validate.ThrowIfNull<T>(T, String)
- Validate.ThrowIfNullOrEmpty(String, String)
- Validate.ThrowIfNullOrEmpty(String, String, Boolean)
- Validate.ThrowIfNullOrEmpty<T>(T, String)
- Validate.ThrowIfNullOrWhiteSpace(String, String)
In each case, the first parameter is the value to be checked, and the second is a string containing its name. In C#, you can get the name using the nameof expression.
Of course these methods will each throw an exception if an unexpected null is found (or other criteria are met), and you'll need to decide how to handle that. The good news is that the exceptions thrown will follow consistent patterns, and the amount of code you need to write is minimised.
The constructors in this example show Validate methods being used in the body of the constructor and also in a chained call. In both cases, the original value is passed through if it's valid:
public class Person
{
public string Name { get; private set; }
public int Age { get; private set; }
public Person(string inName, int inAge)
{
Name = Validate.ThrowIfNullOrWhiteSpace(inName, nameof(inName));
Age = inAge;
}
// Copy constructor
public Person(Person inOther)
: this(Validate.ThrowIfNull(inOther, nameof(inOther)).Name, inOther.Age)
{
// The passthrough functionality of the Validate method
// is used to check for null. If "inOther" has a value, then
// it's returned by the Validate method.
}
}
If you've chosen Maybe:
Using the following method to create a Maybe<T> will resolve CA1062:
The type T can usually be inferred from the type of the value
public void Method(string inCouldBeNull)
{
var theString1 = Maybe.WithValue(inCouldBeNull);
// The type T can usually be inferred; but if required
// it can be explicitly specified:
var theString2 = Maybe.WithValue<string>(inCouldBeNull);
// Other code goes here. There are safe methods
// to get a value or a default from theString
// if needed.
}
The value, whether it's null or not, will be converted to a Maybe of the same underlying type. Of course, Maybe<T> cannot be used for passthrough null-checking in the same way as the Validate methods, because it changes the type of the parameter; but using Maybe<T> more widely through a codebase can have its benefits too, contributing to the overall robustness of the code.
You can read about other benefits of using Maybe<T> in the dedicated article.