Null-check multiple parameters and throw an exception with their name
Asked Answered
D

3

8

I would like to validate multiple parameters and throw an ArgumentNullException if any of them are null. For the sake of argument, let's assume I've got this:

public void DoSomething(SomeClass param1, SomeClass param2, SomeClass param3);

Of course, I could do:

if (param1 == null)
    throw new ArgumentNullException(nameof(param1));
if (param2 == null)
    throw new ArgumentNullException(nameof(param2));
if (param3 == null)
    throw new ArgumentNullException(nameof(param3));

But it's not particularly pretty, especially if it's a recurring check in throughout the application. So, I thought I would do this:

public static class ValidationExtensions
{
    public static void NullCheck<T>(this T subject)
    {
        if (T == null)
            throw new ArgumentNullException();
    }
}

// ...

param1.NullCheck();
param2.NullCheck();
param3.NullCheck();

But this way I lose the nameof. I can't do nameof(subject) as that's meaningless.

Of course, this is an option:

public static class ValidationExtensions
{
    public static void NullCheck<T>(this T subject, string parameterName)
    {
        if (T == null)
            throw new ArgumentNullException(parameterName);
    }
}

// ...

param1.NullCheck(nameof(param1));
param2.NullCheck(nameof(param2));
param3.NullCheck(nameof(param3));

But it seems prone to error, with the repeated params... and, to be honest, just not pretty.

Is there a nice way of doing this? Ideally without using any external libraries.

Delirious answered 6/3, 2019 at 0:4 Comment(3)
param1.NullCheck(nameof(param1)); doesn't seem to be too bad - it's less error prone than your first option. What is error prone is just throwing exceptions on null. It's probably better to try and code without exceptions at all. Read this: Eric Lippert's Vexing Exceptions.Jingoism
@Jingoism that's definitely a good read, but it's more about catching the exceptions, not throwing them. My current use case is mostly about preventing things missing from configs - I'd rather throw early and let the consumer of my package know exactly what they're missing, rather than ignore it and fail less predictably in a later stage. param1.NullCheck(nameof(param1)); is definitely not the end of the world, but it just feels like there should be a way to do it in a way that only refers to param1 once...Delirious
It does say things like "Try to never write a library yourself that throws a vexing exception" and it's not overly hard to extrapolate how to go about writing good code that doesn't raise exceptions. And I don;t know why you say "rather than ignore it and fail less predictably in a later stage" - there's nothing there about ignoring errors. It's hard to give further advice in your case as your code doesn't show any detail within the method.Jingoism
R
4

The most succinct and maintainable solution would be what you have, or C#7 Throw Expression

param1 = param1 ?? throw new ArgumentNullException(nameof(param1));

You could use Expressions and some smarts, though i wouldn't recommend this, its smells and hides simple logic behind an abstraction and overhead. Additionally, it relies on unspecified behaviour that might change in the future

However, all that aside, i give you Expressions

public static class Validator
{
   public static void Validate<T>(Expression<Func<string, T>> f)
   {
      var name = (f.Body as MemberExpression).Member.Name;
      if(f.Compile().Invoke(name) == null)
         throw new ArgumentNullException(name);    
   }
}

The reason this works, is because the compiler generates a class for the lambda expression (a closure) and the local variable becomes a property Member.Name, which means it should also work for properties too (untested)

Usage

public static void Test(string param1, string param2)
{
   Validator.Validate(x => param1);
}

public static void Main()
{
   Test(null,"asdf");
}

Output

Value cannot be null. Parameter name: param1

Note : Truthfully i haven't thought about this too much or tested it more than a couple of use cases, it may or may not work, so i am not responsible for the people you injure with this code

Romona answered 6/3, 2019 at 0:24 Comment(2)
I agree with you, it doesn't seem like the way to go. Thank you for providing a working solution though, even if I won't use it ;) I'll wait some time before accepting just in case someone else chimes in with a better answer.Delirious
an even better answer* ;)Delirious
I
1

In .NET 6 (C# 10) you can use ArgumentNullException.ThrowIfNull.

ArgumentNullException.ThrowIfNull(param1, nameof(param1));
ArgumentNullException.ThrowIfNull(param2, nameof(param2));
ArgumentNullException.ThrowIfNull(param3, nameof(param3));

There is no option for multiple arguments yet, but you can add the following helpers:

public static class ArgumentNullExceptionHelpers
{
    public static void ThrowIfNull(params object[] arguments)
    {
        ArgumentNullException.ThrowIfNull(arguments);

        foreach (object argument in arguments)
        {
            ArgumentNullException.ThrowIfNull(argument);
        }
    }

    public static void ThrowIfNull(
        params (object? Argument, string ParamName)[] value)
    {
        foreach ((object? argument, string paramName) in value)
        {
            ArgumentNullException.ThrowIfNull(argument, paramName);
        }
    }
}

And then call it:

ArgumentNullExceptionHelpers.ThrowIfNull(param1, param2, param3);

// Or - with argument names
ArgumentNullExceptionHelpers.ThrowIfNull(
    (param1, nameof(param1)), (param2, nameof(param2)), (param3, nameof(param3)));
Ipoh answered 20/12, 2021 at 17:6 Comment(1)
Unfortunately this ArgumentNullExceptionHelpers.ThrowIfNull(param1, param2, param3); won't help much, since Intellisense in VisualStudio still thinks the param1 (2, 3) might be null after this line.Expeller
T
0

Beware of those "code optimizations". Reflection and expression trees come with a performance penalty.

Even your generics option leds the developer to make unnecessary use of it. At least, add a constraint to it:

public static void NullCheck<T>(this T subject, string parameterName) where T : class

Of course, this is also not free.

With the introduction of throw as an expression in C# 7.0 (as Michael Randall showed in his answer) it's a one liner and Visual Studio will do that for you.

Tambourin answered 6/3, 2019 at 13:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.