Как решить «проблему с T?» / Обнуляемое ограничение на параметр типа? - PullRequest
2 голосов
/ 26 октября 2019

Я проектирую интерфейс в C # 8.0 с включенным nullable, ориентируясь на .Net Standard 2.0 (используя пакет Nullable ) и 2.1. Сейчас я сталкиваюсь с Проблема с T?.

В моем примере я создаю интерфейс для кэша, который хранит данные Stream s / byteидентифицируется ключом string, т. е. файловая система может быть реализована тривиально. Каждая запись дополнительно идентифицируется версией, которая должна быть общей. Эта версия может быть, например, другой ключ string (например, etag), int или date.

public interface ICache<TVersionIdentifier> where TVersionIdentifier : notnull
{
    // this method should return a nullable version of TVersionIdentifier, but this is not expressable due to 
    // "The issue with T?" https://devblogs.microsoft.com/dotnet/try-out-nullable-reference-types/
    Task<TVersionIdentifier> GetVersionAsync(string file, CancellationToken cancellationToken = default);

    // TVersionIdentifier should be not nullable here, which is what we get with the given code
    Task<Stream> GetAsync(string file, TVersionIdentifier version, CancellationToken cancellationToken = default);

    // ...
}

Хотя я понимаю, что проблема с T? и почему онаэто не тривиальная проблема для компилятора, я не знаю, как справиться с этой ситуацией.

Я думал о некоторых опциях, но ни один из них не кажется оптимальным:

  1. Отключить nullable для интерфейса, вручную помечать ненулевые значения TVersionIdentifier:

    #nullable disable
    public interface ICache<TVersionIdentifier>
    {
        Task<TVersionIdentifier> GetVersionAsync(string file, CancellationToken cancellationToken = default);
        // notice the DisallowNullAttribute
        Task<Stream> GetAsync(string file, [DisallowNull] TVersionIdentifier version, CancellationToken cancellationToken = default);
        // ..
    }
    #nullable restore
    

    Это, похоже, не помогает. При реализации ICache<string> в контексте, допускающем обнуляемость, Task<string?> GetVersionAsync генерирует предупреждение, поскольку подписи не совпадают. Скорее всего, компилятор знает, что тип, заданный для TVersionIdentifier, не имеет значения NULL и применяет свои правила, хотя ICache не знает об этом. Для популярных интерфейсов, таких как IList<T>, это имеет смысл.

    Это приводит к появлению предупреждений, поэтому это не похоже на реальный вариант.

  2. Отключить Nullable для реализациичлен. Хотя в обоих случаях выдаются предупреждения, кажется, что следует отключить nullable для интерфейса, хотя (действительно ли это имеет смысл?).

    #nullable disable
        public Task<string> GetVersionAsync(string file, CancellationToken cancellationToken = default)
        {
            return Task.FromResult((string)null);
        }
    #nullable restore
    
  3. Как (2), но отключитьобнуляемый для всего класса реализации (и интерфейса тоже). Может быть, это наиболее последовательно, так как в нем четко выражено понятие обнуляемых ссылочных типов / generics / ... для этого класса не работает , и вызывающие стороны должны справиться с этой ситуацией, как это было раньше (до C #8.0).

    #nullable disable
    class FileSystemCache : ICache<string>
    {
        // ...
    }
    #nullable restore
    
  4. Опция (2) или (3), но подавление предупреждения компилятора вместо отключения значения NULL. Может быть, впоследствии компилятор делает неправильные выводы, так что это плохая идея?

  5. Как (1), но с соглашением для разработчиков: отключить nullable для интерфейса, но пометить [DisallowNull] и [NotNull] вручную (см. код в (1)). Используйте обнуляемые типы как TVersionIdentifier во всех реализациях вручную (мы не можем принудительно применить это). Это может приблизить нас как можно ближе к правильно аннотированным сборкам. Потребители нашей реализации получают предупреждение при использовании нулей, где они не должны, и они получают правильно аннотированные возвращаемые значения. Этот способ не очень самодокументирован, хотя. Любой возможный исполнитель должен прочитать наши документы, чтобы полностью понять наше намерение. Таким образом, наш интерфейс не является хорошей моделью для возможных реализаций, поскольку он пропускает некоторые аспекты. Люди могут этого не ожидать.

Какой путь? Есть ли другой способ, который я пропустил? Есть ли какие-либо важные аспекты, которые я пропустил?

Я думаю, было бы замечательно, если бы Microsoft посоветовала возможный обходной путь в сообщении в блоге.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...