。NET实现具有可空引用类型的IEnumerator

问题描述 投票:2回答:1

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保留为null值检查空引用的“负担”被转移到库的客户端。诚然,枚举数通常通过foreach语句使用,因此它主要由运行时处理,可能没什么大不了的。确实,从语义上来说,将枚举器的Current属性设置为null是有意义的,因为可能没有要枚举的数据,但是我确实确实看到IEnumerator<T>接口和可为空的引用类型功能之间存在冲突。我真的很想知道是否有办法在保持功能的同时使编译器满意。而且,其他一些语言中具有无效安全机制的约定又有哪些呢?

我意识到这是一个开放性的问题,但我仍然认为这对SO是合适的。预先感谢!

c# .net ienumerator nullablereferencetypes
1个回答
2
投票

我绝对建议将其声明为非null版本。 documentation for Current指出行为是在Current实际上为空的情况下定义的。我认为在这种情况下阅读curCli的任何人的代码中都有错误,因此最好通过异常来掩盖该错误……这确实很容易做到:

Current

[您可能还想在返回public TWebSocketClient Current => curCli ?? throw new InvalidOperationException("Current should not be used in the current state"); 时将curCli设置为null,所以如果在用尽枚举器之后代码访问false,也会引发异常。

[那时,我认为您的代码比Current的编译器生成的代码更好,后者不会引发异常。

© www.soinside.com 2019 - 2024. All rights reserved.