Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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)
like image 252
MatthewToday Avatar asked May 20 '26 23:05

MatthewToday


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
like image 98
TheGeneral Avatar answered May 22 '26 12:05

TheGeneral


I found another solution, inspired from @TheGeneral's answer that I think results in cleaner operation because you can still use the is keyword to pattern match a tuple.

The generic class

public class AsyncParseResult<T> : Tuple<bool, T?>
{
    public AsyncParseResult(bool item1, T? item2) : base(item1, item2) { }

    [MemberNotNullWhen(returnValue: true, member: nameof(Item2))]
    public new bool Item1 { get { return base.Item1; } }

    public new T? Item2 { get { return base.Item2; } }
}

Example Usage

public class Thing
{

}

public static class Test
{
    public static async Task<AsyncParseResult<Thing>> TryParseThingAsync(string value)
    {
        if(value == "thing")//you would replace this with your real parsing logic that uses async
        {
            return new AsyncParseResult<Thing>(true, new Thing());
        }
        else
        {
            return new AsyncParseResult<Thing>(false, null);
        }
    }

    public static async Task ExampleUsage()
    {
        if(await TryParseThingAsync("thing") is (true, var thing))
        {
            //thing will not be null here
        }
    }
}
like image 26
Ryan Adams Avatar answered May 22 '26 13:05

Ryan Adams



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!