Is it possible to refactor this extension method?

Viewed 2806

I have the following extension method:

public static void ThrowIfArgumentIsNull<T>(this T value, string argument) 
    where T : class
{
    if (value == null)
    {
        throw new ArgumentNullException(argument);
    }
}

and this is an example of its usage....

// Note: I've poorly named the argument, on purpose, for this question.
public void Save(Category qwerty)
{
    qwerty.ThrowIfArgumentIsNull("qwerty");
    ....
}

works 100% fine.

But, I don't like how I have to provide the name of the variable, just to help my exception message.

I was wondering if it's possible to refactor the extension method, so it could be called like this...

qwerty.ThrowIfArgumentIsNull();

and it automatically figures out that the name of the variable is 'qwerty' and therefore uses that as the value for the ArgumentNullException.

Possible? I'm assuming reflection could do this?

8 Answers

I like Enforce from the Lokad Shared Libraries.

Basic syntax:

Enforce.Arguments(() => controller, () => viewManager,() => workspace);

This will throw an exception with the parameter name and type if any of the arguments is null.

With C# 10 you can do this ArgumentNullException.ThrowIfNull(argument);

"Is it possible to refactor this extension method?"

As others already said, there's not much you can do without using some AOP involved (like package NullGuard.Fody), but you can spice up your version a little bit to make it more flexible:

public static class Requires
{
    public static T NotNull<T>([NotNull] T? arg, string argName, string? customErrorText = null)
    {
        if (arg is null)
            throw new ArgumentNullException(argName, customErrorText ?? Strings.ArgumentNull(argName));

        return arg;
    }

   // For all types
    public static T NotDefault<T>(T arg, string argName, string? customErrorText = null)
    {
        if (EqualityComparer<T>.Default.Equals(arg, default!))
            throw new ArgumentException(customErrorText ?? Strings.ArgumentHasTypeDefault(argName), argName);

        return arg;
    }

}

// Extensions
public static class GenericTypeParamCheckingExtensions
{
    [return: NotNull]
    public static T NotNull<T>([NotNull] this T? source, string argName, string? customErrorText = null) where T : class =>
        source ?? throw ExceptionsHelper.ArgumentNull(argName, customErrorText);

    // For all types
    public static T NotDefault<T>(this T source, string argName, string? customErrorText = null)
    {
        if (EqualityComparer<T>.Default.Equals(source, default))
            throw ExceptionsHelper.ArgumentDefault(argName, customErrorText);

        return source;
    }

}

// Usage
public class YourClass 
{
    private void YourMethod(string? nullableParam, int nonNullableParam)
    {
        // option 1 - just param checking
        nullableParam.NotNull(nameof(nullableParam));
        nonNullableParam.NotDefault(nameof(nonNullableParam));

        // option 2 - param checking and value retrieval if no exception occurred
        var stringValue = nullableParam           
           .NotNull(nameof(nullableParam));

        var intValue = nonNullableParam 
           .NotDefault(nameof(nonNullableParam), /* optional */ $"My custom error text");
    }
}

I am using many more Methods for various types, like Enumerables, Strings, etc.

You can find the Sourcecode of an older version here: https://github.com/CleanCodeX/Common.Shared.Min

or simply use the Nuget package, which will be updated from time to time so you only have to update the package if you want to. https://www.nuget.org/packages/CCX.Common.Shared.Min/

Related