Apakah ada cara untuk menutup StreamWriter tanpa menutup BaseStream-nya?

117

Akar masalah saya adalah ketika usingmemanggil Disposea StreamWriter, itu juga membuang BaseStream(masalah yang sama dengan Close).

Saya memiliki solusi untuk ini, tetapi seperti yang Anda lihat, ini melibatkan penyalinan aliran. Adakah cara untuk melakukan ini tanpa menyalin aliran?

Tujuannya adalah untuk memasukkan konten string (aslinya dibaca dari database) ke dalam aliran, sehingga aliran dapat dibaca oleh komponen pihak ketiga.
NB : Saya tidak dapat mengubah komponen pihak ketiga.

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

Digunakan sebagai

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

Idealnya, saya mencari metode imajiner yang disebut BreakAssociationWithBaseStream, mis

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;
}
Biner Worrier
sumber
Ini adalah pertanyaan serupa: stackoverflow.com/questions/2620851
Jens Granlund
Saya melakukan ini dengan aliran dari WebRequest, yang menarik, Anda dapat menutupnya jika pengkodeannya adalah ASCII tetapi bukan UTF8. Aneh.
tofutim
tofutim, saya memiliki milik saya yang dikodekan sebagai ASCII, dan masih membuang aliran yang mendasarinya ..
Gerard ONeill

Jawaban:

121

Jika Anda menggunakan .NET Framework 4.5 atau yang lebih baru, ada kelebihan StreamWriter yang dapat digunakan untuk meminta aliran dasar dibiarkan terbuka saat penulis ditutup .

Di versi .NETFramework sebelum 4.5, StreamWriter menganggapnya memiliki aliran. Pilihan:

  • Jangan buang StreamWriter; siram saja.
  • Buat pembungkus streaming yang mengabaikan panggilan ke Close/ Disposetetapi menjadi proxy yang lainnya. Saya memiliki implementasi itu di MiscUtil , jika Anda ingin mengambilnya dari sana.
Jon Skeet
sumber
15
Jelas kelebihan beban 4,5 adalah konsesi yang tidak dipikirkan - kelebihan beban membutuhkan ukuran buffer, yang tidak boleh 0 atau nol. Secara internal saya tahu bahwa 128 karakter adalah ukuran minimum, jadi saya setel ke 1. Jika tidak, 'fitur' ini membuat saya senang.
Gerard ONeill
Apakah ada cara untuk mengatur leaveOpenparameter StreamWriteritu setelah dibuat?
c00000fd
@ c00000fd: Aku tidak menyadarinya.
Jon Skeet
1
@ Yepeekai: "jika saya melewatkan aliran ke metode sub dan metode sub membuat StreamWriter, itu akan dibuang pada akhir pelaksanaan metode sub itu" Tidak, itu tidak benar. Itu hanya akan dibuang jika ada yang memanggilnya Dispose. Metode berakhir tidak melakukannya secara otomatis. Ini dapat diselesaikan nanti jika memiliki finalizer, tetapi itu bukan hal yang sama - dan masih belum jelas bahaya apa yang Anda antisipasi. Jika menurut Anda tidak aman untuk mengembalikan StreamWriterdari suatu metode karena dapat otomatis dibuang oleh GC, itu tidak benar.
Jon Skeet
1
@Yepeekai: Dan IIRC, StreamWritertidak memiliki finalizer - Saya tidak mengharapkannya, karena alasan ini.
Jon Skeet
44

.NET 4.5 mendapat metode baru untuk itu!

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
sumber
Terima kasih sobat! Tidak mengetahui hal ini, dan jika ada sesuatu yang menjadi alasan bagus bagi saya untuk mulai menargetkan .NET 4.5!
Vectovox
22
Sayang sekali tidak ada kelebihan beban yang tidak membutuhkan bufferSize untuk disetel. Saya senang dengan default di sana. Saya harus lulus sendiri. Bukan akhir dari dunia.
Andy McCl Luggage
3
Standarnya bufferSizeadalah 1024. Detailnya ada di sini .
Alex Klaus
35

Cukup tidak memanggil Disposepada StreamWriter. Alasan kelas ini dapat dibuang bukan karena memiliki sumber daya yang tidak terkelola tetapi untuk memungkinkan pembuangan aliran yang dengan sendirinya dapat menampung sumber daya yang tidak terkelola. Jika kehidupan aliran yang mendasari ditangani di tempat lain, tidak perlu membuang penulis.

Darin Dimitrov
sumber
2
@ Marc, tidakkah pemanggilan akan Flushmelakukan pekerjaan jika buffer data?
Darin Dimitrov
3
Baik, tetapi begitu kita keluar dari CreateStream, StreamWrtier dapat dikoleksi, memaksa pembaca bagian ketiga untuk berpacu dengan GC, yang bukan merupakan situasi yang ingin saya tinggalkan. Atau apakah saya melewatkan sesuatu?
Binary Worrier
9
@BinaryWorrier: Tidak, tidak ada syarat balapan: StreamWriter tidak memiliki finalizer (dan memang tidak boleh).
Jon Skeet
10
@ Biner Worrier: Anda seharusnya hanya memiliki finalizer jika Anda memiliki sumber daya secara langsung . Dalam kasus ini, StreamWriter harus berasumsi bahwa Aliran akan membersihkan dirinya sendiri jika perlu.
Jon Skeet
2
Tampaknya metode 'tutup' dari StreamWriter juga menutup dan membuang aliran. Jadi, seseorang harus menyiram, tetapi tidak menutup atau membuang penulis aliran, sehingga tidak menutup aliran, yang akan melakukan hal yang sama dengan membuang aliran. Terlalu banyak "bantuan" dari API di sini.
Gerard ONeill
5

Stream memori memiliki properti ToArray yang dapat digunakan bahkan saat streaming ditutup. To Array menulis konten aliran ke array byte, terlepas dari properti Posisi. Anda dapat membuat aliran baru berdasarkan aliran yang Anda tulis.

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
sumber
Apakah itu dengan benar membatasi array yang dikembalikan ke ukuran konten? Karena Stream.Positiondapat tidak disebut setelah itu dibuang.
Nyerguds
2

Anda perlu membuat turunan StreamWriter dan mengganti metode pembuangannya, dengan selalu meneruskan false ke parameter pembuangan, ini akan memaksa penulis aliran TIDAK untuk menutup, StreamWriter hanya memanggil metode buang dalam metode tutup, jadi tidak perlu menimpanya (tentu saja Anda dapat menambahkan semua konstruktor jika Anda mau, saya hanya punya satu):

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

    protected override void Dispose(bool disposing)
    {
        base.Dispose(false);
    }
}
Aaron Murgatroyd
sumber
3
Saya percaya ini tidak melakukan apa yang Anda pikirkan. The disposingbendera merupakan bagian dari yang IDisposablepola . Selalu meneruskan falseke Dispose(bool)metode kelas dasar pada dasarnya memberi sinyal ke metode StreamWriteryang dipanggil dari finalizer (yang tidak demikian ketika Anda memanggil Dispose()secara eksplisit), dan karenanya tidak boleh mengakses objek yang dikelola. Inilah mengapa ia tidak akan membuang aliran dasar. Namun, cara Anda mencapai ini adalah hack; akan jauh lebih mudah untuk tidak menelepon Disposedulu!
stakx - tidak lagi berkontribusi
Itu benar-benar Symantec, apa pun yang Anda lakukan selain menulis ulang streaming sepenuhnya dari awal akan menjadi retasan. Yang pasti, Anda tidak bisa memanggil base.Dispose (false) sama sekali, tetapi tidak akan ada perbedaan fungsional, dan saya suka kejelasan contoh saya. Namun, perlu diingat, versi kelas StreamWriter di masa mendatang dapat melakukan lebih dari sekadar menutup aliran saat dibuang, jadi memanggil buang (salah) di masa mendatang juga membuktikannya. Tapi untuk masing-masing miliknya sendiri.
Aaron Murgatroyd
2
Cara lain untuk melakukannya adalah dengan membuat pembungkus aliran Anda sendiri yang berisi aliran lain di mana metode Tutup tidak melakukan apa-apa daripada menutup aliran yang mendasarinya, ini bukan peretasan tetapi lebih banyak pekerjaan.
Aaron Murgatroyd
Waktu yang luar biasa: Saya baru saja akan menyarankan hal yang sama (kelas dekorator, mungkin bernama OwnedStream, yang mengabaikan Dispose(bool)dan Close).
stakx - tidak lagi berkontribusi
Ya, kode di atas adalah bagaimana saya melakukannya untuk metode yang cepat dan kotor, tetapi jika saya membuat aplikasi komersial atau sesuatu yang benar-benar penting bagi saya, saya akan melakukannya dengan benar menggunakan kelas pembungkus Stream. Secara pribadi saya pikir Microsoft membuat kesalahan di sini, penulis aliran seharusnya memiliki properti boolean untuk menutup aliran yang mendasarinya, tetapi kemudian saya tidak bekerja di Microsoft sehingga mereka melakukan apa yang mereka suka Saya kira: D
Aaron Murgatroyd