I have implemented an Option
type for some project of mine like this:
public abstract Option<T> {}
public class None<T> : Option<T>
public class Some<T> : Option<T>
{
public T Value { get; }
public Some(T value)
{
Value = value;
}
}
For finding out if an Option contains a value I use this extension method which makes use of pattern matching:
public static bool TryGetValue<T>(this Option<T> option, out T value)
{
if (option is Some<T> some)
{
value = some.Value;
return true;
}
value = default;
return false;
}
I now get the following warning for return default;
Cannot convert null literal to non nullable reference or unconstrained type parameter
It is impossible for me to restrict the generic parameter T
to class
or struct
.
For example, if I restricted the generic parameter to class
, I couldn't generate Option<int?>
instances as the Nullable<int>
type is a struct
itself. Declaring the out parameter as nullable via postfixing ?
is also not a solution as it seems.
To me the type system is somewhat broken or not thoroughly thought through at this stage. Either Nullable should have been a class
or there needs to be a generic parameter restriction like:
public static bool TryGetValue<T>(this Option<T> option, out T value)
where T : nullable [...]
Is there another approach that might be suitable for this issue? What am I missing?
Use the MaybeNullWhenAttribute or the NotNullWhenAttribute. I recommend MaybeNullWhen
since it works even on a type parameter not constrained to a struct or reference type.
public static bool TryGetValue<T>(this Option<T> option, [MaybeNullWhen(false)] out T value)
{
if (option is Some<T> some)
{
value = some.Value;
return true;
}
value = default;
return false;
}
Usage:
if(option.TryGetValue(out var value))
{
value.SomeMethod(); // no warning - value is known to be non-null here
}
value.SomeMethod(); // warning - value may be null here.
The attribute is not available pre .Net standard 2.1/.new core 3.0, but you can manually define it yourself if it's not available. Make sure it's internal, as otherwise if another library also defines it as public and someone inherits from both libraries it will cause conflicts:
namespace System.Diagnostics.CodeAnalysis
{
/// <summary>Specifies that when a method returns <see cref="ReturnValue"/>, the parameter may be null even if the corresponding type disallows it.</summary>
[AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
internal sealed class MaybeNullWhenAttribute : Attribute
{
/// <summary>Initializes the attribute with the specified return value condition.</summary>
/// <param name="returnValue">
/// The return value condition. If the method returns this value, the associated parameter may be null.
/// </param>
public MaybeNullWhenAttribute(bool returnValue) => ReturnValue = returnValue;
/// <summary>Gets the return value condition.</summary>
public bool ReturnValue { get; }
}
(taken from https://github.com/dotnet/runtime/blob/6077cf01f951a711a26a8d5970b211b6031b5158/src/libraries/System.Private.CoreLib/src/System/Diagnostics/CodeAnalysis/NullableAttributes.cs#L45-L60)
There is no pretty way to have a completely generic solution as of today with C# 8.0.
[NotNullWhen()]
attribute is one step forward, but then we will run into following:
A nullable type parameter must be known to be a value type or non-nullable reference type. Consider adding a 'class', 'struct' or type constraint.
I'd say this is a major pain point with nullables now. I hope it will be addressed in 8.1 or something...
Relevant discussion - https://github.com/dotnet/csharplang/issues/2194 - allow generic methods to specify T? without constraining to class or struct.
As a workaround, multiple extension method copies with required where
constraints can be made to cover all possible types.
Since the issue #1628 was fixed, it is now possible to have all overloads in a single extension class. But it still require to double the number of extension methods for every independent generic out parameter. Yikes!
From a quick look, I couldn't find an opened issue specific to the out parameters. Might worth to bring this particular use case to csharplang github issues if that wasn't done yet.
Update: I made a comment in the above-mentioned issue with my take on it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With