8

I am converting a codebase to C#8 with nullable reference types. I came across the a method similar to the one in this question but async.

public async Task<T> GetAsync<T>()
{
    // sometimes returns default(T); => warning CS8603 Possible null reference return
}

T may be any type, including nullable reference types or nullable value types.

To be clear, I understand WHY this method triggers a warning. What I'd like to know is what annotations can be used to resolve it.

  • I know I can use #nullable disable or default(T)!, but I was hoping for something that's less of a "hammer".
  • I know I can't use [return: MaybNull] because that would apply to the Task itself, not the T.

Is there any other attribute/annotation I can apply to make the compiler happy, or is default(T)! my only option?

ChaseMedallion
  • 20,860
  • 17
  • 88
  • 152
  • Please explain in more detail why the [possible duplicate you referred to](https://stackoverflow.com/q/54593923) doesn't address your issue. Have you tried using the null-forgiving operator on the return statement? Given that the other question already points out that that's the way to resolve the warning, why do you think you'd get any different an answer here? Do you understand that the `MaybeNull` annotation and the null-forgiving operator are doing two different things? The former is what fixes the warning, the latter ensures caller are aware of the possibility. – Peter Duniho Mar 27 '20 at 18:03
  • @PeterDuniho the duplicate returns T so they can use return: MaybeNull. In my case I return Task so that attribute would apply to the task, rather than T. As I mentioned in the question, I'm aware of using `default(T)!` as a solution here but it feels like a hack; I'm curious if this is the only solution. – ChaseMedallion Mar 27 '20 at 18:13
  • _"the duplicate returns T so they can use return: MaybeNull"_ -- yes, but that's a wholly separate problem from the warning you're asking about here. Are you trying to ask two questions at the same time? If so, you need to fix the question by asking just one here, and posting a second question for the second question. Hack or not, using null-forgiving _is_ the exact answer, as provided in the likely-duplicate you've already looked at. – Peter Duniho Mar 27 '20 at 18:15
  • @PeterDuniho I am not asking about the warning; I understand why the warning is being omitted. I want to know whether there is a "clean" option to resolve this case. I've updated the wording to hopefully lessen confusion. As you say, it sounds like `!` is the only option in this case, *unlike* in the other question where there is a superior option. – ChaseMedallion Mar 28 '20 at 20:32
  • 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) – Rikki Gibson Nov 22 '20 at 19:11
  • @RikkiGibson seems like the C#9 answer solves this nicely. Of course, I asked this question before C#9 was released! – ChaseMedallion Nov 23 '20 at 23:14

3 Answers3

1

Form my experience, you can use Task<T?> GetAsync<T>() where T: class to resolve your problem.

1

In C# 9 we can solve just by adding ?

public async Task<T?> GetAsync<T>()
{
    return default;
}

But you need to distinguish nullable value types and nullable ref types in the calling code. To get a nullable ref type as a return value you can call the method with either <T> or <T?>:

SomeClass? c = await GetAsync<SomeClass>(); // return type is SomeClass?
SomeClass? c2 = await GetAsync<SomeClass?>(); // return type is SomeClass?

To get a nullable value type you need to call it with <T?>:

int? i = await GetAsync<int?>(); // return type is int?
int i2 = await GetAsync<int>(); // return type is int

P.S. I wonder how Microsoft explains us why they can't allow unconstrained T? and then just does this in the next C# version :)

Another option is to use a code from the answer for C# 8.

Answer for C# 8

We can't have async Task<T?> GetAsync<T>() since SomeClass? and SomeStruct? are very different. Also default! is not the best option since we can get nullable reference on non-nullable reference type by calling GetAsync<SomeClass>() in the calling code.

Better option is to have two different methods that use the same private method:

public class Storage
{
    ...
    public Task<T?> GetClassAsync<T>() where T : class
    {
        return GetAsync<T?>();
    }
    public Task<T?> GetStructAsync<T>() where T : struct
    {
        return GetAsync<T?>();
    }
    private async Task<T> GetAsync<T>()
    {
        if (condition)
            return default!;
        string json = await GetJsonAsync();
        T result = JsonSerializer.Deserialize<T>(json);
        return result;
    }
}

And usage:

// return type is SomeClass?
SomeClass? classResult = await storage.GetClassAsync<SomeClass>();

// return type is int?
int? structResult = await storage.GetStructAsync<int>();


// warning: Nullability of type argument doesn't match 'class' constraint
SomeClass? classResult = await storage.GetClassAsync<SomeClass?>();
// error: The type 'int?' must be a non-nullable value type
int? structResult2 = await storage.GetStructAsync<int?>();
AlbertK
  • 11,841
  • 5
  • 40
  • 36
  • Great to know about the C#9 solution to this! – ChaseMedallion Dec 11 '20 at 19:02
  • I gotta say I think .net is becoming a tangled mess. Just the name of this feature, "nullable reference types", shows how difficult a time they are having creating simple, clear concepts (since all reference types have always been nullable, unlike value types for which "nullable" was at least a semi-sensible shorthand for the System.Nullable struct, which, being a value type, is of course not nullable. I blame the influx of javascript kiddies! :D – Dojo Mar 09 '22 at 11:48
0

From searching around and doing more research, it appears that the preferred way to suppress the warning in this context is to use the ! operator on default(T).

ChaseMedallion
  • 20,860
  • 17
  • 88
  • 152