.NET 实现具有可空引用类型的 IEnumerator
.NET Implement IEnumerator with nullable reference types
自release of C# 8.0, I've really been enjoying the 'void safety' with nullable reference types以来。然而,在调整我的库以支持新功能时,我偶然发现了一个 'problem',我真的无法在任何地方找到答案。我查看了 Microsoft 的发行说明和 .NET 源代码,但没有成功。
TL;DR:问题本质上是 IEnumerator<T>
的 Current
属性 是否应该声明为可为 null 的引用类型。
假设 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
的 return 类型是可为 null 的引用类型。
IEnumerator
接口的设计方式是 'should' 调用 IEnumerator<T>.MoveNext()
方法来预先知道枚举器是否有下一个值,从而实现某种 void安全,但显然,在编译器看来,这没有任何意义,调用 MoveNext()
方法并不能从本质上保证枚举器的 Current
属性 不为空。
我希望我的库在没有警告的情况下进行编译,并且编译器不会让我在构造函数中留下 this.curCli
和 null
值,如果它没有被声明为可为 null 的引用类型如果它被声明为可空,那么检查空引用的 'burden' 将被传输到库的客户端。诚然,枚举数通常通过 foreach
语句使用,因此它主要由运行时处理,可能没什么大不了的。确实,从语义上讲,枚举器的 Current
属性 是 null
是有意义的,因为可能没有要枚举的数据,但我确实看到 IEnumerator<T>
接口和可空引用类型特性。我真的很想知道是否有办法在保持功能的同时让编译器满意。另外,其他一些具有无效安全机制的语言的约定是什么?
我知道这是一个开放式问题,但我仍然认为它适合 SO。提前致谢!
我肯定会建议将其声明为非空版本。 documentation for Current
声明行为是在 curCli
实际上为 null 的情况下定义的。我认为在这些情况下阅读 Current
的任何人在他们的代码中都有错误,最好通过异常来显示该错误......这真的很容易做到:
public TWebSocketClient Current => curCli ??
throw new InvalidOperationException("Current should not be used in the current state");
您可能还想在 return false
时将 curCli
设置为 null
,这样如果代码访问 Current
在用尽枚举数后。
在这一点上,我认为您的代码 比编译器为 yield return
生成的代码 更好,后者不会引发异常。
自release of C# 8.0, I've really been enjoying the 'void safety' with nullable reference types以来。然而,在调整我的库以支持新功能时,我偶然发现了一个 'problem',我真的无法在任何地方找到答案。我查看了 Microsoft 的发行说明和 .NET 源代码,但没有成功。
TL;DR:问题本质上是 IEnumerator<T>
的 Current
属性 是否应该声明为可为 null 的引用类型。
假设 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
的 return 类型是可为 null 的引用类型。
IEnumerator
接口的设计方式是 'should' 调用 IEnumerator<T>.MoveNext()
方法来预先知道枚举器是否有下一个值,从而实现某种 void安全,但显然,在编译器看来,这没有任何意义,调用 MoveNext()
方法并不能从本质上保证枚举器的 Current
属性 不为空。
我希望我的库在没有警告的情况下进行编译,并且编译器不会让我在构造函数中留下 this.curCli
和 null
值,如果它没有被声明为可为 null 的引用类型如果它被声明为可空,那么检查空引用的 'burden' 将被传输到库的客户端。诚然,枚举数通常通过 foreach
语句使用,因此它主要由运行时处理,可能没什么大不了的。确实,从语义上讲,枚举器的 Current
属性 是 null
是有意义的,因为可能没有要枚举的数据,但我确实看到 IEnumerator<T>
接口和可空引用类型特性。我真的很想知道是否有办法在保持功能的同时让编译器满意。另外,其他一些具有无效安全机制的语言的约定是什么?
我知道这是一个开放式问题,但我仍然认为它适合 SO。提前致谢!
我肯定会建议将其声明为非空版本。 documentation for Current
声明行为是在 curCli
实际上为 null 的情况下定义的。我认为在这些情况下阅读 Current
的任何人在他们的代码中都有错误,最好通过异常来显示该错误......这真的很容易做到:
public TWebSocketClient Current => curCli ??
throw new InvalidOperationException("Current should not be used in the current state");
您可能还想在 return false
时将 curCli
设置为 null
,这样如果代码访问 Current
在用尽枚举数后。
在这一点上,我认为您的代码 比编译器为 yield return
生成的代码 更好,后者不会引发异常。