【发布时间】:2020-02-10 19:13:10
【问题描述】:
自从release of C# 8.0 之后,我真的很享受nullable reference types 带来的“虚空安全”。然而,在调整我的库以支持新功能时,我偶然发现了一个“问题”,我真的无法在任何地方找到答案。我查看了 Microsoft 的发行说明和 .NET 源代码,但没有运气。
TL;DR:问题本质上是IEnumerator<T> 的Current 属性是否应声明为可为空的引用类型。
假设IEnumerator<T>的实现如下:
public class WebSocketClientEnumerator<TWebSocketClient> : IEnumerator<TWebSocketClient> where TWebSocketClient : WebSocketClient
{
private WebSocketRoom<TWebSocketClient> room;
private int curIndex;
private TWebSocketClient? curCli;
public WebSocketClientEnumerator(WebSocketRoom<TWebSocketClient> room)
{
this.room = room;
curIndex = -1;
curCli = default(TWebSocketClient);
}
public bool MoveNext()
{
if (++curIndex >= room.Count)
{
return false;
}
else
{
curCli = room[curIndex];
}
return true;
}
public void Reset() { curIndex = -1; }
void IDisposable.Dispose() { }
public TWebSocketClient? Current
{
get { return curCli; }
}
object IEnumerator.Current
{
get { return Current; }
}
}
并假设以下代码消耗枚举器:
public class WebSocketRoom<TWebSocketClient> : ICollection<TWebSocketClient> where TWebSocketClient : WebSocketClient
{
// ...
public void UseEnumerator()
{
var e = new WebSocketClientEnumerator<TWebSocketClient>(this);
bool hasNext = e.MoveNext();
if (hasNext)
{
WebSocketClient c = e.Current; // <= Warning on this line
}
}
// ...
}
代码会产生警告,因为WebSocketClientEnumerator<TWebSocketClient>.Current 的返回类型显然是可以为空的引用类型。
IEnumerator 接口的设计方式是“应该”调用IEnumerator<T>.MoveNext() 方法来事先知道枚举数是否有下一个值,从而实现某种无效安全,但显然,在编译器这意味着什么,调用MoveNext() 方法并不能本质上保证枚举器的Current 属性不为空。
我希望我的库在没有警告的情况下编译,如果它没有被声明为可空引用类型并且如果它被声明为可空,编译器不会让我在构造函数中留下带有 null 值的 this.curCli然后检查空引用的“负担”被转移到图书馆的客户身上。诚然,枚举器通常通过foreach 语句消耗,因此它主要由运行时处理,可能没什么大不了的。确实,从语义上讲,枚举器的Current 属性为null 是有意义的,因为可能没有要枚举的数据,但我确实看到IEnumerator<T> 接口和可空引用类型特性之间存在冲突.我真的想知道是否有办法让编译器满意,同时仍然保持功能。另外,其他一些语言的约定是什么,具有一些无效的安全机制?
我意识到这是一个开放式问题,但我仍然认为它适合 SO。提前致谢!
【问题讨论】:
标签: c# .net ienumerator nullable-reference-types