c# - .NET 使用可为空的引用类型实现 IEnumerator
问题描述
自C# 8.0 发布以来,我一直很享受可空引用类型的“无效安全” 。然而,在调整我的库以支持新功能时,我偶然发现了一个“问题”,我真的无法在任何地方找到答案。我查看了 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()
方法以事先知道枚举数是否具有下一个值,从而实现某种 void 安全,但显然,在编译器看来,这没有任何意义,调用该MoveNext()
方法本质上并不保证枚举数的Current
属性不为空。
我希望我的库在没有警告的情况下编译,如果它没有被声明为可为空的引用类型并且如果它被声明为可为空,那么编译器不会让我在构造函数中留下this.curCli
一个值,那么检查的“负担”null
空引用被传输到库的客户端。诚然,枚举器通常是通过语句消耗foreach
的,因此它主要由运行时处理,它可能没什么大不了的。Current
确实,从语义上讲,枚举器的属性是有意义的,null
因为可能没有要枚举的数据,但我确实看到了IEnumerator<T>
接口和可为空的引用类型功能。我真的想知道是否有办法让编译器满意,同时仍然保持功能。此外,其他一些具有一些无效安全机制的语言的约定是什么?
我意识到这是一个开放式问题,但我仍然认为它适合 SO。提前致谢!
解决方案
我肯定会建议它应该被声明为非空版本。的文档Current
说明行为是在curCli
实际为空的情况下定义的。我认为Current
在这些情况下阅读的任何人的代码中都有错误,最好通过异常来显示该错误......这真的很容易做到:
public TWebSocketClient Current => curCli ??
throw new InvalidOperationException("Current should not be used in the current state");
您可能还希望在返回时设置curCli
为,以便在耗尽枚举数后访问代码时也会引发异常。null
false
Current
在这一点上,我认为你的代码比编译器生成的代码更好yield return
,它不会抛出异常。
推荐阅读
- r - 是否可以对聚合的 dcast 值进行操作?
- javascript - Vue 将选项传递给单独组件中加载的插件
- python-3.x - 将 Azure Functions 部署到 Azure 时出现 bcrypt 错误
- sql - postgreSQL中的PGresult如何成为指针?
- android - 如何在android中不使用zxing创建QR?
- python - Python 多处理,无法使用 pool.map 进行迭代
- html - 页脚浮动到页面中间
- java - 将空文档添加到集合
- php - 如何将数组作为键的值存储在 PHP 中的另一个数组中?
- ssas - 引用另一个表的 SSAS 命名计算