Czy istnieje sposób na zamknięcie StreamWriter bez zamykania jego BaseStream?

117

Moim głównym problemem jest to, że kiedy usingwywołuje Disposea StreamWriter, usuwa również BaseStream(ten sam problem z Close).

Mam obejście tego problemu, ale jak widzisz, wymaga to skopiowania strumienia. Czy można to zrobić bez kopiowania strumienia?

Ma to na celu pobranie zawartości ciągu (pierwotnie odczytanego z bazy danych) do strumienia, aby strumień mógł zostać odczytany przez komponent innej firmy.
Uwaga : nie mogę zmienić komponentu innej firmy.

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;
}

Użyty jako

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

Idealnie szukam wyimaginowanej metody o nazwie BreakAssociationWithBaseStreamnp

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;
}
Binary Worrier
źródło
To jest podobne pytanie: stackoverflow.com/questions/2620851
Jens Granlund Kwietnia
Robiłem to ze strumieniem z WebRequest, co ciekawe, możesz go zamknąć, jeśli kodowanie to ASCII, ale nie UTF8. Dziwne.
tofutim
tofutim, mój kod został zakodowany jako ASCII i nadal pozbywa się podstawowego strumienia ..
Gerard ONeill

Odpowiedzi:

121

Jeśli używasz .NET Framework 4.5 lub nowszego, istnieje przeciążenie StreamWriter, za pomocą którego możesz poprosić o pozostawienie otwartego strumienia podstawowego, gdy moduł zapisujący jest zamknięty .

We wcześniejszych wersjach .NET Framework starszych niż 4.5 StreamWriter zakłada, że jest właścicielem strumienia. Opcje:

  • Nie wyrzucaj StreamWriter; po prostu spłucz to.
  • Utwórz opakowanie strumienia, które ignoruje wywołania do Close/, Disposeale wszystkie inne serwery proxy. Mam implementację tego w MiscUtil , jeśli chcesz ją stamtąd pobrać.
Jon Skeet
źródło
15
Najwyraźniej przeciążenie 4.5 było nie przemyślaną koncesją - przeciążenie wymaga rozmiaru bufora, który nie może wynosić 0 ani null. Wewnętrznie wiem, że 128 znaków to minimalny rozmiar, więc ustawiłem go na 1. W przeciwnym razie ta „funkcja” mnie uszczęśliwia.
Gerard ONeill,
Czy istnieje sposób na ustawienie tego leaveOpenparametru po StreamWriterutworzeniu?
c00000fd
@ c00000fd: Nie jestem tego świadomy.
Jon Skeet,
1
@Yepeekai: "Jeśli przekażę strumień do metody podrzędnej i ta metoda podrzędna utworzy StreamWriter, zostanie on usunięty po zakończeniu wykonywania tej metody podrzędnej" Nie, to po prostu nieprawda. Zostanie usunięty tylko wtedy, gdy coś go wezwie Dispose. Zakończenie metody nie robi tego automatycznie. Może zostać sfinalizowany później, jeśli ma finalizator, ale to nie to samo - i nadal nie jest jasne, jakiego niebezpieczeństwa się spodziewasz. Jeśli uważasz, że zwrócenie StreamWritermetody z metody jest niebezpieczne, ponieważ może zostać automatycznie usunięte przez GC, to po prostu nieprawda.
Jon Skeet
1
@Yepeekai: A IIRC StreamWriternie ma finalizatora - nie spodziewałbym się tego właśnie z tego powodu.
Jon Skeet
44

.NET 4.5 ma nową metodę dla tego!

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
źródło
Dzięki stary! Nie wiedziałem o tym, a jeśli cokolwiek, to byłby to dobry powód, aby zacząć kierować na .NET 4.5!
Vectovox
22
Szkoda, że ​​nie ma przeciążenia, które nie wymaga ustawienia bufferSize. Jestem zadowolony z ustawienia domyślnego. Muszę sam to przekazać. To nie koniec świata.
Andy McCluggage
3
Wartość domyślna bufferSizeto 1024. Szczegóły tutaj .
Alex Klaus
35

Proszę nie dzwonić Disposepo StreamWriter. Powodem, dla którego ta klasa jest dostępna, nie jest to, że zawiera niezarządzane zasoby, ale umożliwia pozbycie się strumienia, który sam może zawierać niezarządzane zasoby. Jeśli żywotność podstawowego strumienia jest obsługiwana w innym miejscu, nie ma potrzeby usuwania osoby zapisującej.

Darin Dimitrov
źródło
2
@Marc, czy wywołanie nie zadziałałoby, gdyby Flushbuforowało dane?
Darin Dimitrov
3
W porządku, ale kiedy wyjdziemy z CreateStream, StreamWrtier jest do zebrania, zmuszając czytelnika trzeciej części do ścigania się z GC, co nie jest sytuacją, w której chcę zostać. A może coś mi brakuje?
Binary Worrier
9
@BinaryWorrier: Nie, nie ma warunku wyścigu: StreamWriter nie ma finalizatora (i rzeczywiście nie powinien).
Jon Skeet
10
@Binary Worrier: finalizator powinien mieć tylko wtedy, gdy bezpośrednio jesteś właścicielem zasobu. W takim przypadku StreamWriter powinien założyć, że Stream wyczyści się, jeśli zajdzie taka potrzeba.
Jon Skeet
2
Wydaje się, że metoda „close” StreamWriter również zamyka i usuwa strumień. Trzeba więc opróżnić, ale nie zamykać ani usuwać streamwritera, aby nie zamykał strumienia, co byłoby równoważne z usunięciem strumienia. Zbyt dużo "pomocy" z API tutaj.
Gerard ONeill
5

Strumień pamięci ma właściwość ToArray, której można używać nawet wtedy, gdy strumień jest zamknięty. To Array zapisuje zawartość strumienia do tablicy bajtów, niezależnie od właściwości Position. Możesz utworzyć nowy strumień na podstawie strumienia, w którym napisałeś.

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;
}
Tudor
źródło
Czy to poprawnie ogranicza zwracaną tablicę do rozmiaru zawartości? Bo Stream.Positionmoże nie być nazywane po jej wyrzucać.
Nyerguds
2

Musisz utworzyć element podrzędny StreamWriter i nadpisać jego metodę dispose, zawsze przekazując false do parametru disposing, wymusi to NIE zamykanie modułu zapisującego strumień, StreamWriter po prostu wywołuje dispose w metodzie close, więc nie ma potrzeby nadpisać (oczywiście możesz dodać wszystkie konstruktory, jeśli chcesz, mam tylko jeden):

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

    protected override void Dispose(bool disposing)
    {
        base.Dispose(false);
    }
}
Aaron Murgatroyd
źródło
3
Uważam, że to nie robi tego, co myślisz, że robi. disposingFlaga jest częścią tej IDisposablestrukturze . Zawsze przekazywanie falsedo Dispose(bool)metody klasy bazowej zasadniczo sygnalizuje StreamWriter, że jest ona wywoływana z finalizatora (co nie jest tak, gdy wywołujesz Dispose()jawnie), a zatem nie powinno uzyskiwać dostępu do żadnych zarządzanych obiektów. To dlatego, że nie będzie wyrzucać strumienia bazowego. Jednak sposób, w jaki to osiągnąłeś, to hack; byłoby o wiele prościej po prostu nie dzwonić Dispose!
stakx - już nie publikuje
To tak naprawdę Symantec, wszystko, co zrobisz, oprócz przepisania strumieniowania całkowicie od zera, będzie hackem. Zdecydowanie jednak po prostu nie można by wywołać base.Dispose (false) w ogóle, ale nie byłoby różnicy funkcjonalnej i podoba mi się klarowność mojego przykładu. Należy jednak pamiętać o tym, że przyszła wersja klasy StreamWriter może zrobić coś więcej niż tylko zamknąć strumień, gdy zostanie usunięty, więc wywołanie dispose (false) future proof również to potwierdza. Ale każdemu jego własne.
Aaron Murgatroyd
2
Innym sposobem na zrobienie tego byłoby utworzenie własnego opakowania strumienia, które zawiera inny strumień, w którym metoda Close po prostu nic nie robi, zamiast zamykać strumień bazowy, jest to mniejszy hack, ale wymaga więcej pracy.
Aaron Murgatroyd
Niesamowite wyczucie czasu: miałem właśnie zasugerować to samo (klasa dekoratorów, prawdopodobnie nazwana OwnedStream, która ignoruje Dispose(bool)i Close).
stakx - nie publikuje już
Tak, powyższy kod pokazuje, jak robię to dla szybkiej i brudnej metody, ale gdybym tworzył komercyjną aplikację lub coś, co naprawdę ma dla mnie znaczenie, zrobiłbym to poprawnie, używając klasy opakowania Stream. Osobiście uważam, że Microsoft popełnił błąd, streamwriter powinien zamiast tego mieć właściwość boolowską, aby zamknąć podstawowy strumień, ale nie pracuję w Microsoft, więc robią to, co lubią, myślę: D
Aaron Murgatroyd