我正在C#8.0中设计一个启用nullable
的接口,目标是.Net Standard 2.0(使用Nullable package)和2.1。我现在正面对 The issue with T?
。
在我的示例中,我正在为缓存构建一个接口,该缓存用于存储Stream
/字节数据,该数据由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?
的问题是什么,为什么它对于编译器来说不是小问题,但我不知道如何处理这种情况。
我想到了一些选择,但它们都不是最佳选择:
为界面禁用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
不知道,为ICache
给出的类型很可能是不可为空的,并强制执行它的规则。对于IList<T>
这样的流行界面,这是有道理的。
这会导致警告,因此这似乎不是一个真正的选择。
为成员的实现禁用null。尽管在这两种情况下都会产生警告,但似乎随后却为此界面禁用了nullable
(这真的有意义吗?)。
#nullable disable
public Task<string> GetVersionAsync(string file, CancellationToken cancellationToken = default)
{
return Task.FromResult((string)null);
}
#nullable restore
类似于(2),但对于整个实现类(以及接口)也禁用nullable。也许这是最必然的结果,因为它明确表达了可空引用类型/泛型/ ...的概念不适用于此类,并且调用者必须像以前一样处理这种情况( C#8.0)。
#nullable disable
class FileSystemCache : ICache<string>
{
// ...
}
#nullable restore
选项(2)或(3),但抑制了编译器警告,而不是禁用可为空的值。也许编译器之后会得出错误的结论,所以这是个坏主意?
类似于(1),但对于实现者具有约定:禁用接口的nullable
,但手动用[DisallowNull]
和[NotNull]
进行注释(请参阅(1)中的代码)。在所有实现中手动将可空类型用作TVersionIdentifier
(我们无法强制执行此操作)。关于正确注释的程序集,这可能使我们尽可能地接近。在不应该使用null的情况下,我们实现的使用者会受到警告,并且他们会获得正确注释的返回值。这种方式虽然不是很自我记录。任何可能的实施者都需要阅读我们的文档以充分理解我们的意图。因此,由于缺少某些方面,因此我们的界面不是适用于可能的实现的良好模型。人们可能不希望这样。
走哪条路?我还有其他想念的方式吗?我错过了任何相关方面吗?
我认为,如果Microsoft在博客文章中建议一种可能的解决方法,那将很棒。