Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TryGetValue pattern with C# 8 nullable reference types

Tags:

c#

nullable

I'm playing with porting some code to C# to enable nullable reference types, and I've encountered some functions in our code that use the TryGetValue pattern.

That is, something like this:

public bool TryGetSession(string key, out Session session) {
    session = null; // assign default
    // code which looks for a session based on the key, etc
    // return true or false if we found the session key
}

The pattern which we're trying to express here is "if the return value is true, then session is non-null. If false, then don't even attempt to look at the session, it's garbage.

The problem is I now get a warning on session = null, but I'm forced to put something there as out parameters MUST be populated by the function.

Is there a good answer here? My thoughts:

I could drop the TryGet pattern and embrace the nullable reference types (This is what other languages like Swift seem to do) e.g.

Session? GetSession(string key);

Or, I could live up to my "non-null" promise using a placeholder value e.g.

public bool TryGetSession(string key, out Session session) {
    session = new InvalidSession(); // assign default
    ...
}

Are there any other options?

like image 924
Orion Edwards Avatar asked Apr 04 '19 22:04

Orion Edwards


2 Answers

If you're arriving at this a little late, like me, it turns out the .NET team addressed it through a bunch of parameter attributes like MaybeNullWhen(returnValue: true) in the System.Diagnostics.CodeAnalysis space which you can use for the try pattern.

Returning a swift-style nullable reference type works well, but the try pattern lets you return things like errors as additional out parameters.

public bool TryGetSession(string key, [NotNullWhen(returnValue: true)] out Session? session, [NotNullWhen(returnValue: false)] out string? error)
{
  // etc
}


// This is okay:
if(TryGetSession("cheese", out var session, out var error))
{
  var time = session.Time;
}

// But this is not:
_ = TryGetSession("cheese", out var session, out var error);
var time = session.Time;
// "CS8602: Dereference of a potentially null reference"

Further details:

  • Documentation about the attributes
  • Language Design Meeting about it.
like image 196
Nick Darvey Avatar answered Nov 04 '22 08:11

Nick Darvey


I originally ended up going with the swift-style Session? GetSession() so the original version of this answer said that.

I later switched to using [NotNullWhen(returnValue: true)] as per Nick Darvey's answer. Because of the out var feature that C# has (and because C# lacks 'if let'), the TryGet pattern ends up being more concise and a better fit.

Note: These attributes only exist in .NET Core 3.0 or greater, they're not in the desktop .NET framework, however you can define them yourself and apply them to your own methods and they work as expected.

like image 36
Orion Edwards Avatar answered Nov 04 '22 08:11

Orion Edwards