1

I have a method with one generic argument, and an optional parameter. The compiler seems to be ignoring the nullability of that type when constructing the default argument. This behavior seems inconsistent with how a default value of local variable is declared with that same type. Why is this?

I would like this to work (use null as the optional parameter) for both reference and value types.

public static void Main()
{
    int? @defaultInt = default;
    Console.WriteLine(defaultInt is null ? "null" : defaultInt); // null
            
    Do<int>(); // 0, not null

}

public static void Do<U>(U? p = default)
{
    Console.WriteLine(p is null ? "null" : p);
}
Adam V. Steele
  • 559
  • 4
  • 18
  • I'm assuming you have nullable reference types enabled? Otherwise that code is invalid. `?` on an unconstrained generic type just means "defaultable" -- it can contain the default value for that type (i.e. `default(U)`). That's a fancy way of saying "Can be a nullable reference if `U` is a reference type; is ignored if `U` is a value type". If you want parameter `p` to be a `Nullable` value type specifically, then you need to constrain `U` to be a value type: `where U : struct`. Alternatively, the caller can specify that `U` is a nullable value type with e.g. `Do()` – canton7 Aug 12 '21 at 12:39
  • Try `default(U?)` instead of `default`, maybe the default literal doesn't pick up the nullable – Xtrem532 Aug 12 '21 at 12:40
  • It's possible, [check it out](https://dotnetfiddle.net/l73rHS). All you need to do is change `default` to `default(U)` so it creates the default of the non-nullable `U` if `U` is a non-nullable type (like `int`) – MindSwipe Aug 12 '21 at 12:52
  • 1
    @Xtrem532 just a FYI, I did try this and it doesn't work. – Adam V. Steele Aug 12 '21 at 12:53
  • You could just do two methods, one constrained to class and one constrained to struct, and then call the same private method from both. – Vance Morgan Aug 12 '21 at 13:05

2 Answers2

2

All you need to do is have 2 overloads with different constraints

public static void Do<U>(U p = default) where U : class
{
    Console.WriteLine(p is null ? "null" : p);
}

public static void Do<U>(U? p = default) where U : struct
{
    Console.WriteLine(p is null ? "null" : p);
}

Live example: https://dotnetfiddle.net/iOzGDb

Jamiec
  • 133,658
  • 13
  • 134
  • 193
  • To make this even better, a link to https://stackoverflow.com/questions/68450941/c-default-of-generic-t-is-not-null-behavior-changes-with-generic-constraint or https://github.com/dotnet/csharplang/blob/main/meetings/2019/LDM-2019-11-25.md#problem-1-t-and-t-mean-different-things or an explanation of _why_ you need to do this would be awesome. – mjwills Aug 12 '21 at 13:19
  • If the generic type is on a class, would this mean you need two copies of the class one with each constraint? – Adam V. Steele Aug 12 '21 at 13:19
  • 1
    @AdamV.Steele short answer to that follow up is yes, you would need 2 classes - one to handle the structs and one to handle the classes. There might be better way(s) to deal with the problem though - you might be straying into [XY Problem](http://xyproblem.info) territory – Jamiec Aug 12 '21 at 13:41
0

This was the idea I had in my comment to your question for sharing the same method.

static void Main(string[] args)
{
    int? defaultInt = default;
    Console.WriteLine(defaultInt is null ? "null" : defaultInt); //null

    Do<string>(); // 0, not null
}

public static void Do<U>(U? p = default) where U : struct
{
    DoIt(p);
}

public static void Do<U>(U? p = default) where U : class
{
    DoIt(p);
}

private static void DoIt<U>(U? p)
{
    Console.WriteLine(p is null ? "null" : p);
}
Vance Morgan
  • 86
  • 1
  • 6