.NET 使用可为空的引用类型实现 IEnumerator
Posted
技术标签:
【中文标题】.NET 使用可为空的引用类型实现 IEnumerator【英文标题】:.NET Implement IEnumerator with nullable reference types 【发布时间】: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。提前致谢!
【问题讨论】:
【参考方案1】:我绝对建议将其声明为非空版本。 documentation for Current
声明行为是在 curCli
实际上为空的情况下定义的。我认为在这些情况下阅读Current
的任何人的代码中都有错误,最好通过异常来显示该错误......这真的很容易做到:
public TWebSocketClient Current => curCli ??
throw new InvalidOperationException("Current should not be used in the current state");
当您返回false
时,您可能还想将curCli
设置为null
,这样如果在枚举数耗尽后代码访问Current
,也会引发异常。
在这一点上,我认为您的代码优于编译器为yield return
生成的代码,它不会引发异常。
【讨论】:
是的,我没有考虑过引发错误,但它实际上很有意义,我还认为在MoveNext()
返回 false 之后阅读 Current
是客户端代码中的错误.好决定!泰!以上是关于.NET 使用可为空的引用类型实现 IEnumerator的主要内容,如果未能解决你的问题,请参考以下文章
将 XDocument.Descendants 与合并运算符一起使用??和可为空的类型