Предупреждение об обнуляемости для Task.FromResult - PullRequest
1 голос
/ 27 апреля 2020

В нашей базе кода есть следующий метод (. NET Стандартная библиотека 2.0):

public Task<T> GetDefaultTask<T>()
{
    return Task.FromResult(default(T));
}

В настоящее время мы пытаемся перейти на C# 8.0 Nullability и получить предупреждение в вышеприведенный код:

предупреждение CS8604: возможный нулевой ссылочный аргумент для параметра 'result' в 'Task Task.FromResult (T result)'.

Почему мы получаем это предупреждение? Для меня это выглядит прекрасно, передать null в качестве параметра Task.FromResult.

Важное замечание: Мы хотим, чтобы Задача содержала нулевое значение. Но добавление Task<T?> заставит нас добавить ограничения типов, которые мы не можем сделать.

Ответы [ 2 ]

2 голосов
/ 27 апреля 2020

Если T является необнуляемым ссылочным типом, null не следует передавать на Task.FromResult<T>. Реализация Task.FromResult не заботится о нулевых ссылках, и вы могли бы использовать Task.FromResult(default(T)!), но тогда вызывающий GetDefaultTask может получить Task<string>, когда на самом деле должно быть Task<string?>. Код типа GetDefaultTask<string>().Result.Length будет компилироваться без предупреждения и вызывать исключения нулевой ссылки во время выполнения.

Насколько я знаю, в настоящее время невозможно правильно аннотировать возвращаемый тип в этой ситуации.

Объявление метод как Task<T?> GetDefaultTask<T>() недопустим, поскольку T может быть либо структурным, либо ссылочным типом, а обнуляемые структуры и ссылочные типы представлены по-разному.

Это можно решить чисто, если T ограничен ссылочным типом:

public Task<T?> GetDefaultTask<T>() where T : class

Но добавление этого ограничения может вызвать дальнейшие проблемы в цепочке вызовов, в зависимости от того, откуда поступает этот параметр T.

Для аналогичных ситуаций где возвращаемое значение generi c может быть структурой или ссылкой (например, Enumerable.FirstOrDefault), есть атрибут [MaybeNull], но его можно применять только к самому возвращаемому значению (в данном случае задаче), а не к универсальный c параметр задачи.

0 голосов
/ 27 апреля 2020

Для меня это выглядит прекрасно, передать нулевой параметр в качестве Task.FromResult.

Нет, это плохая идея.

Если caller указывает ненулевой тип для T, тогда default(T) можно считать «неопределенным» (на самом деле это null, но это главный недостаток реализации C# 8.0 не-обнуляемых ссылочных типов ( т. е. они могут все еще быть null, grrrr.) Рассмотрим:

// Compiled with C# 8.0's non-nullable reference-types enabled.

Task<String> task = GetDefaultTask<String>();
String result = await task;
Console.WriteLine( result.Length ); // <-- NullReferenceException at runtime even though the C# compiler reported `result` cannot be null.

Избегайте использования default / default(T) в C# 8.0 для типов generi c без адекватные ограничения типов.

Существует несколько решений этой проблемы:

1: укажите значение по умолчанию, предоставляемое вызывающим абонентом:

public Task<T> GetDefaultTask<T>( T defaultValue )
{
    return Task.FromResult( defaultValue );
}

Так что вызов сайт должен быть обновлен, и компилятор C# выдаст предупреждение или ошибку, если вызывающая сторона попытается использовать null вместо исключения во время выполнения:

Task<String> task = GetDefaultTask<String>( defaultValue: null ); // <-- compiler error or warning because `null` cannot be used here.
String result = await task;
Console.WriteLine( result.Length );

2: Добавить структуру против ограничений класса в различных методах:

default(T) типа struct / value может быть значимым (или может быть столь же опасным, как null ...), поскольку мы можем безопасно использовать default(T), где T : struct, но не default(T), где T : class, мы можем добавить различные перегрузки для этого случая:

public Task<T> GetDefaultTask<T>()
    where T : struct
{
    return Task.FromResult( default(T) );
}

public Task<T> GetDefaultTask<T>( T defaultValue )
    where T : class
{
    return Task.FromResult( defaultValue );
}

( Обратите внимание, что вы не можете перегружать методы, основанные исключительно на обобщенных типовых ограничениях c - вы можете перегружать только обобщенные c счетчики параметров и обычные типы параметров.

...