Чи є спосіб закрити StreamWriter, не закриваючи його BaseStream?


117

Моя коренева проблема полягає в тому, що під час usingвиклику Disposeна a 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. Дивно.
тофутим

До того, у мене була закодована як ASCII, і вона все ще розпоряджається основним потоком.
Gerard ONeill

Відповіді:


121

Якщо ви використовуєте .NET Framework 4.5 або пізнішої версії, є перевантаження StreamWriter, за допомогою якої ви можете попросити базовий потік залишити відкритим, коли записник закритий .

У більш ранніх версіях .NET Framework до 4.5, StreamWriter передбачається, що йому належить потік. Параметри:

  • Не розпоряджайтеся StreamWriter; просто змийте його.
  • Створіть обтікач потоку, який ігнорує дзвінки до Close/, Disposeале надає проксі для всього іншого. У мене це є реалізацією в MiscUtil , якщо ви хочете схопити його звідти.

15
Очевидно, що 4,5 перевантаження було не продуманою поступкою - для перевантаження потрібен розмір буфера, який не може бути ні 0, ні нульовим. Всередині я знаю, що 128 символів - це мінімальний розмір, тому я просто встановив його на 1. Інакше ця функція робить мене щасливою.
Джерард ONeill

Чи є спосіб встановити цей leaveOpenпараметр після StreamWriterстворення?
c00000fd

@ c00000fd: Не те, про що я знаю.
Джон Скіт

1
@Yepeekai: "Якщо я передаю потік суб-методу і цей метод методу створить StreamWriter, він буде розміщений в кінці виконання цього методу" Ні, це просто не відповідає дійсності. Він буде розпоряджатися, лише якщо щось закликає Dispose. Закінчений метод робить це не автоматично. Він може бути завершений пізніше, якщо він має фіналізатор, але це не те саме - і досі не ясно, яку небезпеку ви передбачаєте. Якщо ви вважаєте, що повернути a StreamWriterз методу небезпечно, тому що він міг автоматично розпоряджатися GC, це просто неправда.
Джон Скіт

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
)

Дякую, друже! Я цього не знав, і якщо що-небудь, це було б гарною причиною для мене почати націлювати на .NET 4.5!
Vectovox

22
Соромно, що немає перевантаження, яка не потребує встановлення bufferSize. Я задоволений дефолтом там. Мені довелося це самому пройти. Не кінець світу.
Енді Макклугдж

3
Типовим bufferSizeє 1024. Деталі тут .
Олексій Клаус

35

Просто не називайте Disposeна StreamWriter. Причина цього класу є одноразовою не тому, що він містить некерований ресурс, а щоб дозволити розпоряджатися потоком, який сам міг би містити некеровані ресурси. Якщо життя основного потоку ведеться в іншому місці, не потрібно розпоряджатися письменникові.


2
@Marc, не закликав би Flushвиконувати цю роботу, якщо вона буферизує дані?
Дарин Димитров

3
Чудово, але як тільки ми виходимо з CreateStream, StreamWrtier збирає колекцію, змушуючи третю частину читача змагатися проти GC, що не є ситуацією, в якій я хочу залишитись. Або мені щось не вистачає?
Бінарний страшник

9
@BinaryWorrier: Ні, немає гоночних умов: StreamWriter не має фіналізатора (і справді не повинен).
Джон Скіт

10
@Binary Worrier: Фіналізатор повинен мати лише у тому випадку, якщо ви безпосередньо володієте ресурсом. У цьому випадку StreamWriter повинен припустити, що Потік очиститься сам, якщо потрібно.
Джон Скіт

2
Здається, що метод «закриття» StreamWriter також закриває і розпоряджається потоком. Таким чином, ви повинні промити, але не закривати та не розпоряджатися транслятором, щоб він не закривав потік, що могло б зробити еквівалент розпорядження потоком. Тут занадто багато "допомоги" від API.
Джерард 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 і змінити його метод розпорядження, завжди передаючи помилковий параметр disposition, він змусить записувач потоку НЕ закриватися, StreamWriter просто закликає розпоряджатися розпорядженням методом закриття, тому немає необхідності замініть це (звичайно, ви можете додати всі конструктори, якщо хочете, у мене просто є):

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 помилився тут. Стрітмейзер повинен був мати булеву властивість замість цього закривати базовий потік, але тоді я не працюю в Microsoft, щоб вони робили те, що їм подобається: D
Аарон Мургатройд
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.