Is there an equivalent NotNullWhen C# pattern for async tuple return?

Viewed 635

In C# with nullable types it is possible to implement a 'TryGet' that is smart about null checking, e.g.,

bool TryGetById(int id, [NotNullWhen(returnValue: true)] out MyThing? myThing)

which allows the caller to skip null checking on the out var myThing.

Unfortunately, Async does not allow out parameters, and the pattern of using a Tuple return does not allow for this smart NotNull checking (at least, not so far as I have found). Is there an alternative?

Is there any way to use a 'NotNullWhen' equivalent on an async Tuple return type e.g.,

Task<(bool Ok, [NotNullWhen(returnValue: true)] MyThing? MyThing)> TryGetById(int id)
2 Answers

There isn't an implementation of this for value Tuples (yet). However! From C#9 You could roll-your-own struct (or even better C#10 record struct) with MemberNotNullWhen.

MemberNotNullWhenAttribute Class

Specifies that the method or property will ensure that the listed field and property members have non-null values when returning with the specified return value condition.

Note : You will need to reimplement all the tupley goodness like equality etc.

Worlds most contrived example ensues

#nullable enable

public readonly struct Test
{
   [MemberNotNullWhen(returnValue: true, member: nameof(Value))]
   public bool IsGood => Value != null;

   public string? Value { get; init; }
}

public static Task<Test> TryGetAsync()
   => Task.FromResult(new Test {Value = "bob"});

public static void TestMethod(string bob)
   => Console.WriteLine(bob);

Usage

var result = await TryGetAsync();
if (result.IsGood)
   TestMethod(result.Value); // <= no warning

If you own the implementation of MyThing, you can do this

public class MyThing
{
    public static readonly MyThing Empty = new();
 // all other properties etc
}

then make your method signature that it can never return null Mything

public async Task<(bool Ok, MyThing MyThing)> TryGetById(int id)
{
    var something = await FindSomething(id);

    return (something == null) 
           ? (false, MyThing.Empty);
           : new (true, something);
}

var result = await TryGetById(420);
if(result.Ok) // whatever

Related