Как правильно обработать исключение в конструкторе одноразового объекта - PullRequest
0 голосов
/ 22 апреля 2020

Допустим, у меня есть этот объект без неуправляемых ресурсов, и я не нуждаюсь или не хочу использовать финализаторы:

public sealed class SealedDisposableClass : IDisposable
{
    private readonly SemaphoreSlim _readLock = new SemaphoreSlim(1, 1);
    private readonly SemaphoreSlim _writeLock = new SemaphoreSlim(1, 1);
    private bool _isDisposed;

    public void Dispose()
    {
        if (_isDisposed)
            return;

        _readLock.Dispose();
        _writeLock.Dispose();

        _isDisposed = true;
    }
}

Теперь давайте предположим гипотетическую ситуацию, когда конструктор SemaphoreSlim иногда может генерировать исключение. И давайте предположим, что когда мы создаем новый экземпляр SealedDisposableClass, компилятор решает инициализировать поля в следующем порядке:

_readLock = new SemaphoreSlim(1, 1);
_writeLock = new SemaphoreSlim(1, 1);

Теперь давайте предположим, что первая строка выполняется успешно, и мы получаем ссылку на управляемый объект _readLock, хранящуюся в члене _readLock , И давайте предположим, что вторая строка терпит неудачу за исключением. Таким образом, мы не получаем ссылку _writeLock. И мы даже не получаем экземпляр SealedDisposableClass. Все просто бросает на стройку. И все же где-то в памяти создан один экземпляр SemaphoreSlim.

Как правильно справиться с этой ситуацией? Должен ли я всегда делать try / catch в конструкторе? Должен ли я добавить try / catch для четности SemaphoreSlim (или, может быть, всегда безопасно создавать объекты SemaphoreSlim?). Есть ли более элегантный способ справиться с этим?

Спасибо.

1 Ответ

0 голосов
/ 22 апреля 2020

Вы можете справиться с этим при помощи try / catch в конструкторе. Если вы проверяете наличие внутренних объектов перед тем, как избавиться от них, вы можете использовать метод dispose для очистки.

public sealed class SealedDisposableClass : IDisposable
{
    private readonly SemaphoreSlim _readLock;
    private readonly SemaphoreSlim _writeLock;
    private bool _isDisposed = false;

    public SealedDisposableClass()
    {
        try
        {
            _readLock = new SemaphoreSlim(1, 1);
            _writeLock = new SemaphoreSlim(1, 1);
        }
        catch
        {
            Dispose();
            throw;
        }
    }


    public void Dispose()
    {
        if (_isDisposed)
            return;

        _readLock?.Dispose();
        _writeLock?.Dispose();

        _isDisposed = true;
    }
}

В качестве альтернативы вы можете выполнить двухэтапную конструкцию, в которой сначала создаете экземпляр объекта и затем инициализируйте его перед фактическим использованием. Это довольно часто встречается в некоторых местах, хотя я нахожу это менее удобным для пользователя.

public void Initialize()
{
    _readLock = new SemaphoreSlim(1, 1);
    _writeLock = new SemaphoreSlim(1, 1);
}

...

using(var x = new SealedDisposableClass())
{
    x.Initialize();
}
...