There is a pattern in C# classes exemplified by Dictionary.TryGetValue
and int.TryParse
: a method that returns a boolean indicating success of an operation and an out parameter containing the actual result; if the operation fails, the out parameter is set to null.
Let's assume I'm using C# 8 non-nullable references and want to write a TryParse method for my own class. The correct signature is this:
public static bool TryParse(string s, out MyClass? result);
Because the result is null in the false case, the out variable must be marked as nullable.
However, the Try pattern is generally used like this:
if (MyClass.TryParse(s, out var result))
{
// use result here
}
Because I only enter the branch when the operation succeeds, result should never be null in that branch. But because I marked it as nullable, I now have to either check for that or use !
to override:
if (MyClass.TryParse(s, out var result))
{
Console.WriteLine("Look: {0}", result.SomeProperty); // compiler warning, could be null
Console.WriteLine("Look: {0}", result!.SomeProperty); // need override
}
This is ugly and a bit unergonomic.
Because of the typical usage pattern, I have another option: lie about the result type:
public static bool TryParse(string s, out MyClass result) // not nullable
{
// Happy path sets result to non-null and returns true.
// Error path does this:
result = null!; // override compiler complaint
return false;
}
Now the typical usage becomes nicer:
if (MyClass.TryParse(s, out var result))
{
Console.WriteLine("Look: {0}", result.SomeProperty); // no warning
}
but atypical usage doesn't get the warning it should:
else
{
Console.WriteLine("Fail: {0}", result.SomeProperty);
// Yes, result is in scope here. No, it will never be non-null.
// Yes, it will throw. No, the compiler won't warn about it.
}
Now I'm not sure which way to go here. Is there an official recommendation from the C# language team? Is there any CoreFX code already converted to non-nullable references that could show me how to do this? (I went looking for TryParse
methods. IPAddress
is a class that has one, but it hasn't been converted on the master branch of corefx.)
And how does generic code like Dictionary.TryGetValue
deal with this? (Possibly with a special MaybeNull
attribute from what I found.) What happens when I instantiate a Dictionary
with a non-nullable value type?
MyClass?
), and do a switch on it with acase MyClass myObj:
and (an optionall)case null:
.