How to solve “The issue with T?”/nullable constraint on type parameter?

久未见 提交于 2020-12-05 11:10:50

问题


I'm designing an interface in C# 8.0 with nullable enabled, targeting .Net Standard 2.0 (using the Nullable package) and 2.1. I am now facing The issue with T?.

In my example, I am building an interface for a cache which stores Streams/byte data, identified by a string key, i.e. the file system could by a trivial implementation. Every entry is additionally identified by a version, which should be generic. This version could for example be another string key (like an etag), an int or a 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);

    // ...
}

While I understand what the issue with T? is and why it is a non trivial problem for the compiler, I don't know how to handle this situation.

I thought of some options but neither of them seems to be optimal:

  1. Disable nullable for the interface, manually tag non-nullable occurrences of 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
    

    This does not seem to help. When implementing ICache<string> in a nullable-enabled context, Task<string?> GetVersionAsync generates a warning as the signatures don't match. Most likely the compiler knows that the type given for TVersionIdentifier is non-nullable and enforces it's rules, even though ICache doesn't know about it. For popular interfaces like IList<T> this makes sense.

    This results in warnings so this does not seem to be a real option.

  2. Disable nullable for the implementation of the member. While warnings are produced in either case it seems consequent to disable nullable for the interface then though (does this really make sense?).

    #nullable disable
        public Task<string> GetVersionAsync(string file, CancellationToken cancellationToken = default)
        {
            return Task.FromResult((string)null);
        }
    #nullable restore
    
  3. Like (2) but disable nullable for the whole implementing class (and the interface too). Maybe this is most consequent, as it clearly expresses the concept of nullable reference types/generics/... does not work for this class and the callers have to handle this situation as they had to earlier (pre C# 8.0).

    #nullable disable
    class FileSystemCache : ICache<string>
    {
        // ...
    }
    #nullable restore
    
  4. Option (2) or (3) but suppressing the compiler warning instead of disabling nullable. Maybe the compiler draws wrong conclusions afterwards, so this is a bad idea?

  5. Like (1) but with convention for implementers: Disable nullable for the interface, but annotate with [DisallowNull] and [NotNull] manually (see code in (1)). Use nullable types as TVersionIdentifier in all implementations manually (we can not enforce this). This might get us as close as we can get regarding correctly annotated assemblies. The consumers of our implemementation get warned when using nulls where they shouldn't and they get correctly annotated return values. This way is not very self-documenting though. Any possible implementer needs to read our docs to fully understand our intent. Thus, our interface isn't a good model for possible implementations, as it misses some aspects. People might not expect this.

Which way to go? Is there another way I missed? Are there any relevant aspects I missed?

I think it would have been great if Microsoft advised a possible workaround in the blog post.

来源:https://stackoverflow.com/questions/58569906/how-to-solve-the-issue-with-t-nullable-constraint-on-type-parameter

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!