jeudi 18 juin 2020

c# 8 nullability - generic class "knowing" its nullability

I've got two classes - an inner class and an outer class, where the inner class can return a null value and an outer class that shouldn't (assuming, of course, that the type parameter T represents a non-nullable type):

#nullable enable
public class Insider<T>
{
    [MaybeNull, AllowNull]
    public T Value {get; set; }

    public Insider()
    { Value = default; }
}
public class Outsider<T>
{
    Insider<T> Inside = new Insider<T>();

    public T Value
    {
        get
        {
            return Inside.Value; // Compiler warning "'Value' may be null here"
        }
        set { Inside.Value = value; }
    }
}
public class Fred
{ }
public static class Test
{
    public static void Stuff()
    {
        Outsider<Fred> one = new Outsider<Fred>();
        Debug.WriteLine("one.Value is " + one.Value);

        Outsider<Fred?> two = new Outsider<Fred?>();
        Debug.WriteLine("two.Value is " + two.Value);

        Outsider<int> three = new Outsider<int>();
        Debug.WriteLine("three.Value is " + three.Value);
    }
}
#nullable disable

When I run this, it gives the expected output:

one.Value is
two.Value is
three.Value is 0

The program state that causes "Insider.Value" to be null is a specific error state, and the caller should know the system is in that state and not access "Outsider.Value", which means that caller should never see "Outsider.Value" return null, but I'd like to enforce that.

And yes, both classes should handle structs as well as classes.

What I was thinking, was something along these lines:

public class Outsider<T>
{
    Insider<T> Inside = new Insider<T>();

    public T Value
    {
        get
        {
            if ((Inside.Value == null)&&(T_shouldnt_be_null))
                throw new Exception("Nope");
            return Inside.Value;
        }
        set { Inside.Value = value; }
    }
}

The troublesome part, of course, is the "T_shouldnt_be_null".

If I put more debugging in Outsider.Value:

public class Outsider<T>
{
    Insider<T> Inside = new Insider<T>();

    public T Value
    {
        get
        {
            Type? nullableType = Nullable.GetUnderlyingType(typeof(T));
            Debug.WriteLine("      typeof(T) is "  + typeof(T) + "; nullableType=" + ((nullableType==null)?"null": nullableType.ToString()));
            foreach (var x in typeof(T).CustomAttributes)
                Debug.WriteLine("         " + x);
            return Inside.Value;
        }
        set { Inside.Value = value; }
    }
}

I get results that were not what I expected. The results when the type parameter T is Fred (non-nullable) are:

typeof(T) is BTSDataCollator.Fred; nullableType=null

and the results when the type parameter is Fred? (nullable) are:

typeof(T) is BTSDataCollator.Fred; nullableType=null

The compiler has completely stripped the nullability indicator from the type. And in both cases the class type has no CustomAttributes (and that's what this post relies on to determine nullability)

Is there any way that a (generic) class can get information on the nullability of its actual type parameter (Fred vs. Fred?) ?





Aucun commentaire:

Enregistrer un commentaire