Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

out parameters with nullable reference types

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?

like image 974
Sebastian Krogull Avatar asked Dec 13 '18 10:12

Sebastian Krogull


2 Answers

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)

like image 196
Yair Halberstadt Avatar answered Nov 08 '22 12:11

Yair Halberstadt


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.

like image 28
Killy.MXI Avatar answered Nov 08 '22 10:11

Killy.MXI