Есть ли способ закрыть StreamWriter, не закрывая его BaseStream?

117

Моя основная проблема в том, что при usingвызове Disposea StreamWriterон также устраняет BaseStream(ту же проблему с Close).

У меня есть обходной путь, но, как видите, он включает в себя копирование потока. Есть ли способ сделать это без копирования потока?

Цель этого - получить содержимое строки (изначально считанной из базы данных) в поток, чтобы поток мог быть прочитан сторонним компонентом.
NB : я не могу изменить сторонний компонент.

public System.IO.Stream CreateStream(string value)
{
    var baseStream = new System.IO.MemoryStream();
    var baseCopy = new System.IO.MemoryStream();
    using (var writer = new System.IO.StreamWriter(baseStream, System.Text.Encoding.UTF8))
    {
        writer.Write(value);
        writer.Flush();
        baseStream.WriteTo(baseCopy); 
    }
    baseCopy.Seek(0, System.IO.SeekOrigin.Begin);
    return baseCopy;
}

Используется в качестве

public void Noddy()
{
    System.IO.Stream myStream = CreateStream("The contents of this string are unimportant");
    My3rdPartyComponent.ReadFromStream(myStream);
}

В идеале я ищу воображаемый метод BreakAssociationWithBaseStream, например,

public System.IO.Stream CreateStream_Alternate(string value)
{
    var baseStream = new System.IO.MemoryStream();
    using (var writer = new System.IO.StreamWriter(baseStream, System.Text.Encoding.UTF8))
    {
        writer.Write(value);
        writer.Flush();
        writer.BreakAssociationWithBaseStream();
    }
    return baseStream;
}
Бинарная тревога
источник
Это похожий вопрос: stackoverflow.com/questions/2620851
Йенс Гранлунд
Я делал это с потоком из WebRequest, что интересно, вы можете закрыть его, если кодировка - ASCII, но не UTF8. Weird.
тофутим
tofutim, у меня мой был закодирован как ASCII, и он по-прежнему избавляется от основного потока ..
Джерард Онилл

Ответы:

121

Если вы используете .NET Framework 4.5 или более поздней версии , существует перегрузка StreamWriter, с помощью которой вы можете попросить, чтобы основной поток оставался открытым при закрытии модуля записи .

В более ранних версиях .NET Framework до 4.5 StreamWriter предполагается , что он владеет потоком. Параметры:

  • Не выбрасывайте StreamWriter; просто промойте его.
  • Создайте оболочку потока, которая игнорирует вызовы Close/, Disposeно проксирует все остальное. У меня есть реализация этого в MiscUtil , если вы хотите получить ее оттуда.
Джон Скит
источник
15
Ясно, что перегрузка 4.5 была не продуманной уступкой - перегрузка требует размера буфера, который не может быть ни 0, ни нулевым. Внутренне я знаю, что 128 символов - это минимальный размер, поэтому я просто установил его на 1. В противном случае эта «особенность» меня радует.
Джерард Онилл
Есть ли способ установить этот leaveOpenпараметр после StreamWriterсоздания?
c00000fd
@ c00000fd: Я не знаю.
Джон Скит
1
@Yepeekai: «если я передаю поток вспомогательному методу, и этот вспомогательный метод создает StreamWriter, он будет удален в конце выполнения этого вспомогательного метода» Нет, это просто неправда. Он будет удален только в том случае, если что-то Disposeего потребует . Окончание метода не делает этого автоматически. Он может быть завершен позже, если в нем есть финализатор, но это не одно и то же - и все еще не ясно, какую опасность вы ожидаете. Если вы считаете, что возвращать a StreamWriterиз метода небезопасно, потому что он может быть автоматически удален сборщиком мусора, это не так.
Джон Скит
1
@Yepeekai: И у IIRC StreamWriterнет финализатора - я бы не ожидал, что он появится именно по этой причине.
Джон Скит
44

В .NET 4.5 для этого появился новый метод!

http://msdn.microsoft.com/EN-US/library/gg712853(v=VS.110,d=hv.2).aspx

public StreamWriter(
    Stream stream,
    Encoding encoding,
    int bufferSize,
    bool leaveOpen
)
maliger
источник
Спасибо друг! Не знал об этом, и если что-нибудь, это было бы для меня прекрасной причиной начать ориентироваться на .NET 4.5!
Vectovox
22
Жаль, что нет перегрузки, которая не требует установки bufferSize. Меня там дефолт устраивает. Я должен пройти это сам. Не конец света.
Энди МакКлаггэдж
3
По умолчанию bufferSizeэто 1024. Подробности здесь .
Alex Klaus
35

Просто не обращайтесь Disposeк StreamWriter. Причина, по которой этот класс является одноразовым, заключается не в том, что он содержит неуправляемые ресурсы, а в том, чтобы разрешить удаление потока, который сам может содержать неуправляемые ресурсы. Если жизнь базового потока обрабатывается где-то еще, нет необходимости удалять писатель.

Дарин Димитров
источник
2
@Marc, разве вызов не Flushсделает работу, если он буферизует данные?
Дарин Димитров
3
Хорошо, но как только мы выходим из CreateStream, StreamWrtier можно собирать, что вынуждает читателя третьей части участвовать в гонке против сборщика мусора, а это не та ситуация, в которой я хочу оставаться. Или я что-то упускаю?
Binary Worrier
9
@BinaryWorrier: Нет, состояния гонки нет: StreamWriter не имеет финализатора (да и не должен).
Джон Скит,
10
@Binary Worrier: у вас должен быть финализатор, только если вы напрямую владеете ресурсом. В этом случае StreamWriter должен предположить, что Stream очистит себя, если это потребуется.
Джон Скит,
2
Кажется, что метод «close» StreamWriter также закрывает и удаляет поток. Таким образом, нужно сбрасывать, но не закрывать и не удалять модуль записи потока, чтобы он не закрывал поток, что эквивалентно удалению потока. Здесь слишком много "помощи" от API.
Gerard ONeill
5

Поток памяти имеет свойство ToArray, которое можно использовать, даже когда поток закрыт. To Array записывает содержимое потока в байтовый массив независимо от свойства Position. Вы можете создать новый поток на основе записанного вами потока.

public System.IO.Stream CreateStream(string value)
{
    var baseStream = new System.IO.MemoryStream();
    var baseCopy = new System.IO.MemoryStream();
    using (var writer = new System.IO.StreamWriter(baseStream, System.Text.Encoding.UTF8))
    {
        writer.Write(value);
        writer.Flush();
        baseStream.WriteTo(baseCopy); 
    }
    var returnStream = new System.IO.MemoryStream( baseCopy.ToArray());
    return returnStream;
}
тюдоровский
источник
Правильно ли это ограничивает возвращаемый массив размером содержимого? Потому что неStream.Position может быть вызван после его удаления.
Nyerguds
2

Вам нужно создать потомка StreamWriter и переопределить его метод удаления, всегда передавая false параметру удаления, он заставит средство записи потока НЕ ​​закрыться, StreamWriter просто вызывает dispose в методе close, поэтому нет необходимости переопределите его (конечно, вы можете добавить все конструкторы, если хотите, у меня только один):

public class NoCloseStreamWriter : StreamWriter
{
    public NoCloseStreamWriter(Stream stream, Encoding encoding)
        : base(stream, encoding)
    {
    }

    protected override void Dispose(bool disposing)
    {
        base.Dispose(false);
    }
}
Аарон Мургатройд
источник
3
Я считаю, что это не то, что вы думаете. disposingФлаг является частью в IDisposableшаблоне . Постоянный переход falseк Dispose(bool)методу базового класса в основном сигнализирует о том, StreamWriterчто он вызывается из финализатора (что не так, когда вы вызываете Dispose()явно), и, следовательно, не должен обращаться к каким-либо управляемым объектам. Вот почему он не удаляет основной поток. Однако способ, которым вы этого добились, - это взлом; было бы гораздо проще вообще не звонить Dispose!
stakx - больше не вносит свой вклад
Это действительно Symantec, все, что вы делаете, кроме полного переписывания потоковой передачи с нуля, будет взломом. Конечно, вы можете просто не вызывать base.Dispose (false) вообще, но функциональной разницы не будет, и мне нравится ясность моего примера. Однако имейте это в виду, будущая версия класса StreamWriter может делать больше, чем просто закрывать поток при удалении, поэтому вызов dispose (false) в будущем также подтверждает это. Но каждому свое.
Аарон Мургатройд,
2
Другой способ сделать это - создать собственную оболочку потока, которая содержит другой поток, в котором метод Close просто ничего не делает, вместо закрытия базового потока, это меньше взлома, но требует больше работы.
Аарон Мургатройд,
Удивительное время: я как раз собирался предложить то же самое (класс декоратора, возможно, названный OwnedStream, который игнорирует Dispose(bool)и Close).
stakx - больше не вносит свой вклад
Да, в приведенном выше коде я делаю это для быстрого и грязного метода, но если бы я создавал коммерческое приложение или что-то, что действительно имело для меня значение, я бы сделал это правильно, используя класс-оболочку Stream. Лично я думаю, что Microsoft допустила здесь ошибку, у Streamwriter должно было быть логическое свойство, чтобы закрыть базовый поток вместо этого, но тогда я не работаю в Microsoft, поэтому они делают то, что им нравится, я думаю: D
Аарон Мургатройд