14

After adding <Nullable>enable</Nullable> or #nullable enable, I ran into the following problem with my Generic methods:

This does not work:

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

enter image description here

This works with warning:

public T GetDefault<T>()
{
   return default;
}

enter image description here

This works individually, but not together.

public T? GetDefault<T>() where T : class
{
    return default;
}

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

enter image description here

Logically, the first method should work.
What is the correct way (in any framework) out of this situation without creating several methods and suppressing warnings?
[MaybeNull] attribute only works with .Net Core 3.0+.

Also, I asked this questions here

Konstantin S.
  • 1,307
  • 14
  • 19
  • What do you mean by `does not work`? – Chetan May 20 '20 at 19:35
  • I added screenshots – Konstantin S. May 20 '20 at 20:18
  • I voted to reopen the question. The "duplicate" has nothing to do with the nullable references feature. – György Kőszeg May 20 '20 at 20:19
  • Yes, it’s strange that it was closed. This is a new feature of the language and some developers do not know about it. – Konstantin S. May 20 '20 at 20:20
  • There a numbers of duplicates of this behavior already. Does this answer your question? [Nullable reference types: How to specify "T?" type without constraining to class or struct](https://stackoverflow.com/questions/55975211/nullable-reference-types-how-to-specify-t-type-without-constraining-to-class) as well as [Nullable reference types with generic return type](https://stackoverflow.com/questions/54593923/nullable-reference-types-with-generic-return-type). There were asked more one year ago, and can be found after a few minutes of googling, there is nothing new already. – Pavel Anikhouski May 21 '20 at 10:10
  • [MaybeNull] attribute only works with .Net Core 3.0+. – Konstantin S. May 21 '20 at 13:27

3 Answers3

13

T? can only be used when the type parameter is known to be of a reference type or of a value type. Otherwise, we don't know whether to see it as a System.Nullable<T> or as a nullable reference type T.

Instead you can express this scenario in C# 8 by using the [MaybeNull] attribute.

#nullable enable
using System.Diagnostics.CodeAnalysis;

public class C
{
    [return: MaybeNull]
    public T GetDefault<T>()
    {
        return default!; // ! just removes warning
    }
}

This attribute is only included in .NET Core 3.0+, but it is possible to declare and use the attribute internal to your project (although this is not officially supported, there's no reason to assume the behavior will break down the line). To do so, you can just add a namespace+class declaration to your code similar to the following:

namespace System.Diagnostics.CodeAnalysis
{
    /// <summary>Specifies that an output may be null even if the corresponding type disallows it.</summary>
    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)]
    internal sealed class MaybeNullAttribute : Attribute { }
}
Konstantin S.
  • 1,307
  • 14
  • 19
Rikki Gibson
  • 4,136
  • 23
  • 34
  • [MaybeNull] attribute only works with .Net Core 3.0+. I did not find a way to do this for the .Net Standard 2.0 project. – Konstantin S. May 21 '20 at 15:36
  • You can define that attribute in your project. The nullability attributes are simple:https://github.com/dotnet/runtime/blob/master/src/libraries/System.Private.CoreLib/src/System/Diagnostics/CodeAnalysis/NullableAttributes.cs – Julien Couvreur May 21 '20 at 18:26
  • Will it be visible outside the nuget library (without the public modifier, because it can cause a conflict)? – Konstantin S. May 21 '20 at 18:29
  • It appears that prominent libraries such as Newtonsoft.Json have internal nullability attributes in pre-netcoreapp3.0 builds. I think internal types will not conflict with types in other assemblies. – Rikki Gibson May 21 '20 at 21:03
  • Yes, it works. > "The compiler is looking for an attribute on the type with the full name (no assembly though) of System.Diagnostics.CodeAnalysis.MaybeNullAttribute. It isn't looking for a specific type. This is similar to how async works in earlier versions of .net framework. The internal attribute would have the same name." – Konstantin S. May 21 '20 at 21:10
  • This answer is almost the same with answers in linked duplicate https://stackoverflow.com/questions/54593923/nullable-reference-types-with-generic-return-type – Pavel Anikhouski May 25 '20 at 21:35
9

Explanation of the problem

The problem in your first code sample occurs because compiler differently handles nullable value types and nullable reference types:

  • Nullable value type T? is represented by type Nullable<T>.
  • Nullable reference type T? is the same type T but with a compiler-generated attribute annotating it.

Compiler cannot generate code to cover this both cases at the same time, therefore a compilation error occurs. And this error forces us to specify class or struct constraint. This behavior is also stated in the C# specification:

For a type parameter T, T? is only allowed if T is known to be a value type or known to be a reference type.

A good explanation of this problem can be found in this article: Try out Nullable Reference Types. Scroll to the paragraph "The issue with T?".


A workaround to fix the problem

The next workaround can be used if you don't want to create two methods with different names and suppress warnings:

// An overload that will be used by reference types.
public T? GetDefault<T>(T? t = default) where T : class
{
    return default;
}

// An overload that will be used by value types.
public T? GetDefault<T>(T? t = default) where T : struct
{
    return default;
}

Here we added an argument t to the methods GetDefault to make compiler being able to differ these two methods. Now we can use methods GetDefault and compiler will define which overload to use. The drawback of this approach is that GetDefault methods have unusable argument t.

Iliar Turdushev
  • 4,935
  • 1
  • 10
  • 23
5

It seems the best solution to this problem will only be in C# 9 as T??

Links:
1. https://github.com/dotnet/csharplang/issues/3471#issuecomment-631722668
2. https://github.com/dotnet/csharplang/issues/3297

At the moment, a working solution was provided by Rikki Gibson. It implies additional code, but it works as it should.

Konstantin S.
  • 1,307
  • 14
  • 19