IDisposable в цепочке конструкторов

#c# #.net #idisposable

#c# #.net #idisposable

Вопрос:

У меня есть следующие два конструктора:

 public Source(FileStream fileStream) {
    // Stuff
}

public Source(String fileName) : this(new FileStream(fileName, FileMode.Open)) {
    // Nothing, the other constructor does the work
}
  

Проблема со вторым конструктором довольно очевидна, FileStream создается и используется, но не удаляется. Поскольку он находится внутри цепочки конструкторов, using блок невозможен. Я не могу переместить new FileStream() в тело конструктора, потому что, хотя он тогда будет находиться в using блоке, логика другого конструктора не сможет быть вызвана. Я не могу извлечь эту логику, потому что она изменяет readonly поля. Я мог бы дублировать логику в каждом конструкторе, но это, очевидно, не очень хорошее решение.

Я действительно предпочел бы сохранить синтаксический сахар, предоставляемый вторым конструктором. Как я могу лучше всего это сделать? Или это просто плохая идея?

Ответ №1:

Взгляните на StreamReader реализацию, которая имеет два типа ctors:

 public StreamReader(Stream stream)
      : this(stream, true)
    {
    }

public StreamReader(string path)
      : this(path, true)
    {
    }
  

Внутренне они оба вызывают один и тот же Init метод с параметром leaveOpen , который устанавливается true равным для первого ctor и false для второго ctor, и на основе этого параметра Stream выполняется (или нет) удаление.

Итак, вы можете сделать что-то вроде этого:

 public class Source : IDisposable
{
    private readonly Stream _stream;
    private readonly bool _leaveOpen;

    private Source(Stream stream, bool leaveOpen)
    {
        _stream = stream;
        _leaveOpen = leaveOpen;
    }

    public Source(FileStream fileStream) : this(fileStream, true)
    {

    }

    public Source(string fileName) : this(new FileStream(fileName, FileMode.Open), false)
    {

    }

    public void Dispose()
    {
        if (!_leaveOpen)
        {
            _stream?.Dispose();
        }
    }
}
  

Комментарии:

1. Хотя это правильно, это намного сложнее для того, что, я думаю, хочет OP (поскольку им, похоже, не нужно обращаться к файловым прошлым конструкторам)

Ответ №2:

Я не совсем уверен, что мешает вам избавиться от него в конструкторе, который принимает FileStream :

 public Source(FileStream fileStream) {
    try
    {
        // Stuff
    }
    finally
    {
        fileStream.Dispose();
    }
}

public Source(String fileName) : this(new FileStream(fileName, FileMode.Open)) {
    // Nothing, the other constructor does the work
}
  

Если это связано с тем, что вы хотите Stream сохранить вызов для вызывающих FileStream конструктор, вы можете добавить третий private конструктор:

 public Source(FileStream fileStream): this(fileStream, disposeStream: false) {
    // Nothing, the other constructor does the work
}

public Source(String fileName) : this(new FileStream(fileName, FileMode.Open), disposeStream: true) {
    // Nothing, the other constructor does the work
}

private Source(FileStream fileStream, bool disposeStream) {
    try
    {
        // Stuff
    }
    finally
    {
        if (disposeStream)
        {
            fileStream.Dispose();
        }
    }
}
  

Комментарии:

1. github.com/DotNetAnalyzers/IDisposableAnalyzers/blob/master/… Я пишу библиотеку, предназначенную для использования третьими лицами. Удаление чьего-либо объекта — абсолютно ужасная вещь. Время жизни объекта — это то, чем они должны управлять, а не я для них. Это хорошо известная проблема, за которую вы выступаете.

2. @Patrick Я не уверен, что понимаю — разве мое второе предложенное решение не справляется с этим? Он удаляет ваш собственный объект, но не их. Я действительно не понимаю, как я выступаю за это.