.NET Реализация IEnumerator с обнуляемыми ссылочными типами - PullRequest
2 голосов
/ 14 октября 2019

После выпуска 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() метод, чтобы заранее знать, имеет ли перечислитель следующее значение, тем самым обеспечивая некоторую пустотную безопасность, но, очевидно, для компилятора это ничего не значит, вызов метода MoveNext() не гарантирует, что перечислитель Current свойство не равно нулю.

Я хотел бы, чтобы моя библиотека компилировалась без предупреждений, а компилятор не позволил мне оставить this.curCli со значением nullЕсли в конструкторе он не объявлен как ссылочный тип, допускающий значение NULL, и если он объявлен как NULL, то «бремя» проверки нулевой ссылки передается клиенту библиотеки. Конечно, перечислители, как правило, потребляются через операторы foreach, следовательно, они в основном управляются средой выполнения и, вероятно, не так уж и сложны. Верно, что с точки зрения семантики, имеет смысл, чтобы свойство Current перечислителя имело значение null, потому что не может быть данных для перечисления, но я действительно вижу конфликт между интерфейсом IEnumerator<T> и функцией обнуляемого ссылочного типа,Мне действительно интересно, есть ли способ сделать компилятор счастливым, сохраняя при этом функциональность. А также, каковы соглашения на некоторых других языках с некоторыми механизмами безопасности void?

Я понимаю, что это своего рода открытый вопрос, но я все еще думаю, что он подходит для SO. Заранее спасибо!

1 Ответ

3 голосов
/ 14 октября 2019

Я бы определенно предположил, что он должен быть объявлен как ненулевая версия. Документация для 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, который не вызывает исключений.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...