7

I have a nullable c# 10 .net 6 project with an extension method to ThrowIfNull

using System;
using System.Runtime.CompilerServices;

#nullable enable
public static class NullExtensions
{
    public static T ThrowIfNull<T>(
        this T? argument, 
        string? message = default, 
        [CallerArgumentExpression("argument")] string? paramName = default
    )
    {
        if (argument is null)
        {
            throw new ArgumentNullException(paramName, message);
        }
        else
        {
            return argument;
        }
    }
}

The extension method implicitly converts string? to string but it does not work for other primitive types like int? or bool?

public class Program
{
    public static void Main()
    {
        Console.WriteLine("Hello World");
        
        string? foo = "foo";
        string nonNullableFoo = foo.ThrowIfNull();  // success from "string?" to "string"
        Console.WriteLine(nonNullableFoo);
        
        bool? baz = true;
        bool nonNullableBaz = baz.ThrowIfNull();    // success from "string?" to "string"
        Console.WriteLine(nonNullableFoo);
        
        int? bar = 2;
        int nonNullableBar = bar.ThrowIfNull(); // error: Cannot implicitly convert type 'int?' to 'int'
        Console.WriteLine(nonNullableBar);
    }
}

How do I make the extension implicitly convert int? and bool??

Here is the full dotnet fiddle https://dotnetfiddle.net/LiQ8NL

Ssh Quack
  • 647
  • 10
  • 13

3 Answers3

6

You can achieve your goal by providing one extension method for non-nullable reference types and another for unmanaged (e.g. int, bool, ...) types. Note that unmanaged types require a cast.

public static class NullExtensions
{
    public static T ThrowIfNull<T>(
        this T? argument,
        string? message = default,
        [CallerArgumentExpression("argument")] string? paramName = default
    ) where T : notnull
    {
        if (argument is null)
        {
            throw new ArgumentNullException(paramName, message);
        }
        else
        {
            return argument;
        }
    }

    public static T ThrowIfNull<T>(
        this T? argument,
        string? message = default,
        [CallerArgumentExpression("argument")] string? paramName = default
    ) where T : unmanaged
    {
        if (argument is null)
        {
            throw new ArgumentNullException(paramName, message);
        }
        else
        {
            return (T)argument;
        }
    }
}

Use like this:

int? foo = 42;
int bar = foo.ThrowIfNull();
Console.WriteLine(bar);

string? baz = "Hello";
string quus = baz.ThrowIfNull();
Console.WriteLine(quus);

// Comment out either this
baz = null;
quus = baz.ThrowIfNull();
// Or this
foo = null;
bar = foo.ThrowIfNull();
Eric J.
  • 147,927
  • 63
  • 340
  • 553
  • 1
    TIL: `notnull` and `managed`. Accepting this as the answer. Thanks! Full fiddle for future reference is here https://dotnetfiddle.net/fjwIo7 – Ssh Quack Dec 21 '21 at 04:28
0
Using the null-coalescing Operator ??

To assign a nullable value to a non-null variable, consider the following code:

int? value = 28;
int result = value ?? -1;
Console.WriteLine($"The result is {result}");
  • Output: The result is 28
int? value = null;
int result = value ?? -1;
Console.WriteLine($"The result is {result}");
  • Output: The result is -1

Editing The Code

Rearrange the code as follows. Therefore bool? You can use the type implicitly:

int? bar = 2;
int nonNullableBar = bar ?? -1; 
Console.WriteLine(nonNullableBar);
bool? baz = true;
bool nonNullableBaz = false;
        
if (baz == true){ 
    nonNullableBaz = true;
} 
else if(baz == false){ 
    nonNullableBaz = false;
} 
else {
    /* Something */
}

References
Sercan
  • 4,739
  • 3
  • 17
  • 36
  • This is explicitly casting a nullable value (int?) to the same nullable value (int?) which is not what I wanted. I want to implicitly cast a nulalble value (int?) into non-nullable value (int) – Ssh Quack Dec 21 '21 at 03:59
  • This is the first time I've come across this usage. This is the answer you're looking for. – Sercan Dec 21 '21 at 04:09
  • Assigning a magic value like -1 to represent null isn't a good practice. – Eric J. Dec 21 '21 at 04:25
  • Can you explain why? – Sercan Dec 21 '21 at 04:27
  • What if -1 is valid input? The issue is particularly apparent when you consider `bool?` – Eric J. Dec 21 '21 at 04:30
  • `bool` in basic data type `??` operator is unavailable. In an `int` data type, if the variable takes the value -1, doesn't that mean it's not `null`? – Sercan Dec 21 '21 at 04:35
0

Found one possible option in MS dotnet docs https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/generics/constraints-on-type-parameters

Constraint "where T : struct" The type argument must be a non-nullable value type

Also according to this answer https://stackoverflow.com/a/8745492/2877168 it is not possible to make this work for string? and all other value types. So only way is to define two extension methods. Here is the updated version that works for string? and int?, bool? etc

public static class NullExtensions
{
    public static T ThrowIfNull<T>(this T? argument, string? message = default, [CallerArgumentExpression("argument")] string? paramName = default)
        where T : struct
    {
        if (argument is null)
            throw new ArgumentNullException(paramName, message);
        return (T)argument;
    }

    public static string ThrowIfNull(this string? argument, string? message = default, [CallerArgumentExpression("argument")] string? paramName = default)
    {
        if (argument is null)
            throw new ArgumentNullException(paramName, message);
        return argument;
    }
}

A working version of this is at https://dotnetfiddle.net/uBX1w6

Ssh Quack
  • 647
  • 10
  • 13