Search code examples
c#generics.net-corenullable

C#: Nullable Generic Types, unexpected behavior


I was wondering why this was the case, as it went against what I would have expected. This has to do with how Generic Nullable types are interpreted in C#.

  public static int? MyFunction(){
    return default;
  }

  public static T MyFunctionGeneric<T>() {
    return default;
  }

  public static T? MyFunctionGenericNullable<T>(){
    return default;
  }

  public static void main(string[] args){
    Console.WriteLine(MyFunction());
    Console.WriteLine(MyFunctionGeneric<int?>());
    Console.WriteLine(MyFunctionGenericNullable<int>());
  }

As expected, when I define the return type as int? the default returned value is null.

This is also the case when I define a generic that returns its own type, because int? is passed in, the default returned value is null.

However, in the third case, even though the return type is technically int?, the default returned value is 0. I would have to pass in int? as my type parameter to get a null response. Note that this happens even when I explicitly state return default(T?)

This is a little bit unexpected, does anyone have an explanation for it?


Solution

  • It's all very confusing because as the docs state:

    The rules are necessarily detailed because of history and the different implementation for a nullable value type and a nullable reference type.

    reading on in the docs, you see an answer to your question (in bold):

    If the type argument for T is a reference type, T? references the corresponding nullable reference type. For example, if T is a string, then T? is a string?.

    If the type argument for T is a value type, T? references the same value type, T. For example, if T is an int, the T? is also an int.

    If the type argument for T is a nullable reference type, T? references that same nullable reference type. For example, if T is a string?, then T? is also a string?.

    If the type argument for T is a nullable value type, T? references that same nullable value type. For example, if T is a int?, then T? is also a int?.

    If we want different behavior, we need to use generic constraints. The documentation doesn't list all useful constraints which in your case would be where T:struct

    This would be compiled with int? as return type if used with int as T

    public static T? MyFunctionGenericNullable<T>()
    where T : struct {
        return default;
    }
    

    and you would be getting the null like in the first two cases.