The Labs \ Source Viewer \ SSCLI \ System.IO \ StreamWriter

  1. // ==++==
  2. //
  3. //
  4. // Copyright (c) 2006 Microsoft Corporation. All rights reserved.
  5. //
  6. // The use and distribution terms for this software are contained in the file
  7. // named license.txt, which can be found in the root of this distribution.
  8. // By using this software in any fashion, you are agreeing to be bound by the
  9. // terms of this license.
  10. //
  11. // You must not remove this notice, or any other, from this software.
  12. //
  13. //
  14. // ==--==
  15. /*============================================================
  16. **
  17. ** Class:  StreamWriter
  18. **
  19. **
  20. ** Purpose: For writing text to streams in a particular
  21. ** encoding.
  22. **
  23. **
  24. ===========================================================*/
  25. using System;
  26. using System.Text;
  27. using System.Threading;
  28. using System.Globalization;
  29. using System.Runtime.Versioning;
  30. using System.Runtime.Serialization;
  31. namespace System.IO
  32. {
  33.     // This class implements a TextWriter for writing characters to a Stream.
  34.     // This is designed for character output in a particular Encoding,
  35.     // whereas the Stream class is designed for byte input and output.
  36.     //
  37.     [Serializable()]
  38.     [System.Runtime.InteropServices.ComVisible(true)]
  39.     public class StreamWriter : TextWriter
  40.     {
  41.         // For UTF-8, the values of 1K for the default buffer size and 4K for the
  42.         // file stream buffer size are reasonable & give very reasonable
  43.         // performance for in terms of construction time for the StreamWriter and
  44.         // write perf. Note that for UTF-8, we end up allocating a 4K byte buffer,
  45.         // which means we take advantage of adaptive buffering code.
  46.         // The performance using UnicodeEncoding is acceptable.
  47.         private const int DefaultBufferSize = 1024;
  48.         // char[]
  49.         private const int DefaultFileStreamBufferSize = 4096;
  50.         private const int MinBufferSize = 128;
  51.        
  52.         // Bit bucket - Null has no backing store. Non closable.
  53.         public static new readonly StreamWriter Null = new StreamWriter(Stream.Null, new UTF8Encoding(false, true), MinBufferSize, false);
  54.        
  55.         internal Stream stream;
  56.         private Encoding encoding;
  57.         private Encoder encoder;
  58.         internal byte[] byteBuffer;
  59.         internal char[] charBuffer;
  60.         internal int charPos;
  61.         internal int charLen;
  62.         internal bool autoFlush;
  63.         private bool haveWrittenPreamble;
  64.         private bool closable;
  65.         // For Console.Out - should Finalize call Dispose?
  66.         // The high level goal is to be tolerant of encoding errors when we read and very strict
  67.         // when we write. Hence, default StreamWriter encoding will throw on encoding error.
  68.         // Note: when StreamWriter throws on invalid encoding chars (for ex, high surrogate character
  69.         // D800-DBFF without a following low surrogate character DC00-DFFF), it will cause the
  70.         // internal StreamWriter's state to be irrecoverable as it would have buffered the
  71.         // illegal chars and any subsequent call to Flush() would hit the encoding error again.
  72.         // Even Close() will hit the exception as it would try to flush the unwritten data.
  73.         // May be we can add a DiscardBufferedData() method to get out of such situation (like
  74.         // StreamRerader though for different reason). Eitherway, the buffered data will be lost!
  75.         private static Encoding _UTF8NoBOM;
  76.        
  77.         static internal Encoding UTF8NoBOM {
  78.             get {
  79.                 if (_UTF8NoBOM == null) {
  80.                     // No need for double lock - we just want to avoid extra
  81.                     // allocations in the common case.
  82.                     UTF8Encoding noBOM = new UTF8Encoding(false, true);
  83.                     Thread.MemoryBarrier();
  84.                     _UTF8NoBOM = noBOM;
  85.                 }
  86.                 return _UTF8NoBOM;
  87.             }
  88.         }
  89.        
  90.        
  91.         internal StreamWriter() : base(null)
  92.         {
  93.             // Ask for CurrentCulture all the time
  94.         }
  95.        
  96.         public StreamWriter(Stream stream) : this(stream, UTF8NoBOM, DefaultBufferSize)
  97.         {
  98.         }
  99.        
  100.         public StreamWriter(Stream stream, Encoding encoding) : this(stream, encoding, DefaultBufferSize)
  101.         {
  102.         }
  103.        
  104.         // Creates a new StreamWriter for the given stream. The
  105.         // character encoding is set by encoding and the buffer size,
  106.         // in number of 16-bit characters, is set by bufferSize.
  107.         //
  108.         public StreamWriter(Stream stream, Encoding encoding, int bufferSize) : base(null)
  109.         {
  110.             // Ask for CurrentCulture all the time
  111.             if (stream == null || encoding == null)
  112.                 throw new ArgumentNullException((stream == null ? "stream" : "encoding"));
  113.             if (!stream.CanWrite)
  114.                 throw new ArgumentException(Environment.GetResourceString("Argument_StreamNotWritable"));
  115.             if (bufferSize <= 0)
  116.                 throw new ArgumentOutOfRangeException("bufferSize", Environment.GetResourceString("ArgumentOutOfRange_NeedPosNum"));
  117.            
  118.             Init(stream, encoding, bufferSize);
  119.         }
  120.        
  121.         // For non closable streams such as Console.Out
  122.         internal StreamWriter(Stream stream, Encoding encoding, int bufferSize, bool closeable) : this(stream, encoding, bufferSize)
  123.         {
  124.             closable = closeable;
  125.         }
  126.        
  127.         [ResourceExposure(ResourceScope.Machine)]
  128.         [ResourceConsumption(ResourceScope.Machine)]
  129.         public StreamWriter(string path) : this(path, false, UTF8NoBOM, DefaultBufferSize)
  130.         {
  131.         }
  132.        
  133.         [ResourceExposure(ResourceScope.Machine)]
  134.         [ResourceConsumption(ResourceScope.Machine)]
  135.         public StreamWriter(string path, bool append) : this(path, append, UTF8NoBOM, DefaultBufferSize)
  136.         {
  137.         }
  138.        
  139.         [ResourceExposure(ResourceScope.Machine)]
  140.         [ResourceConsumption(ResourceScope.Machine)]
  141.         public StreamWriter(string path, bool append, Encoding encoding) : this(path, append, encoding, DefaultBufferSize)
  142.         {
  143.         }
  144.        
  145.         [ResourceExposure(ResourceScope.Machine)]
  146.         [ResourceConsumption(ResourceScope.Machine)]
  147.         public StreamWriter(string path, bool append, Encoding encoding, int bufferSize) : base(null)
  148.         {
  149.             // Ask for CurrentCulture all the time
  150.             if (path == null || encoding == null)
  151.                 throw new ArgumentNullException((path == null ? "path" : "encoding"));
  152.             if (bufferSize <= 0)
  153.                 throw new ArgumentOutOfRangeException("bufferSize", Environment.GetResourceString("ArgumentOutOfRange_NeedPosNum"));
  154.            
  155.             Stream stream = CreateFile(path, append);
  156.             Init(stream, encoding, bufferSize);
  157.         }
  158.        
  159.         private void Init(Stream stream, Encoding encoding, int bufferSize)
  160.         {
  161.             this.stream = stream;
  162.             this.encoding = encoding;
  163.             this.encoder = encoding.GetEncoder();
  164.             if (bufferSize < MinBufferSize)
  165.                 bufferSize = MinBufferSize;
  166.             charBuffer = new char[bufferSize];
  167.             byteBuffer = new byte[encoding.GetMaxByteCount(bufferSize)];
  168.             charLen = bufferSize;
  169.             // If we're appending to a Stream that already has data, don't write
  170.             // the preamble.
  171.             if (stream.CanSeek && stream.Position > 0)
  172.                 haveWrittenPreamble = true;
  173.             closable = true;
  174.         }
  175.        
  176.         [ResourceExposure(ResourceScope.Machine)]
  177.         [ResourceConsumption(ResourceScope.Machine)]
  178.         private static Stream CreateFile(string path, bool append)
  179.         {
  180.             FileMode mode = append ? FileMode.Append : FileMode.Create;
  181.             FileStream f = new FileStream(path, mode, FileAccess.Write, FileShare.Read, DefaultFileStreamBufferSize, FileOptions.SequentialScan);
  182.             return f;
  183.         }
  184.        
  185.         public override void Close()
  186.         {
  187.             Dispose(true);
  188.             GC.SuppressFinalize(this);
  189.         }
  190.        
  191.         protected override void Dispose(bool disposing)
  192.         {
  193.             try {
  194.                 // We need to flush any buffered data if we are being closed/disposed.
  195.                 // Also, we never close the handles for stdout & friends. So we can safely
  196.                 // write any buffered data to those streams even during finalization, which
  197.                 // is generally the right thing to do.
  198.                 if (stream != null) {
  199.                     // Note: flush on the underlying stream can throw (ex., low disk space)
  200.                     if (disposing || (!Closable && stream is __ConsoleStream)) {
  201.                         Flush(true, true);
  202.                     }
  203.                 }
  204.             }
  205.             finally {
  206.                 // Dispose of our resources if this StreamWriter is closable.
  207.                 // Note: Console.Out and other such non closable streamwriters should be left alone
  208.                 if (Closable && stream != null) {
  209.                     try {
  210.                         // Attempt to close the stream even if there was an IO error from Flushing.
  211.                         // Note that Stream.Close() can potentially throw here (may or may not be
  212.                         // due to the same Flush error). In this case, we still need to ensure
  213.                         // cleaning up internal resources, hence the finally block.
  214.                         if (disposing)
  215.                             stream.Close();
  216.                     }
  217.                     finally {
  218.                         stream = null;
  219.                         byteBuffer = null;
  220.                         charBuffer = null;
  221.                         encoding = null;
  222.                         encoder = null;
  223.                         charLen = 0;
  224.                         base.Dispose(disposing);
  225.                     }
  226.                 }
  227.             }
  228.         }
  229.        
  230.         public override void Flush()
  231.         {
  232.             Flush(true, true);
  233.         }
  234.        
  235.         private void Flush(bool flushStream, bool flushEncoder)
  236.         {
  237.             // flushEncoder should be true at the end of the file and if
  238.             // the user explicitly calls Flush (though not if AutoFlush is true).
  239.             // This is required to flush any dangling characters from our UTF-7
  240.             // and UTF-8 encoders.
  241.             if (stream == null)
  242.                 __Error.WriterClosed();
  243.            
  244.             // Perf boost for Flush on non-dirty writers.
  245.             if (charPos == 0 && !flushStream && !flushEncoder)
  246.                 return;
  247.            
  248.             if (!haveWrittenPreamble) {
  249.                 haveWrittenPreamble = true;
  250.                 byte[] preamble = encoding.GetPreamble();
  251.                 if (preamble.Length > 0)
  252.                     stream.Write(preamble, 0, preamble.Length);
  253.             }
  254.            
  255.             int count = encoder.GetBytes(charBuffer, 0, charPos, byteBuffer, 0, flushEncoder);
  256.             charPos = 0;
  257.             if (count > 0)
  258.                 stream.Write(byteBuffer, 0, count);
  259.             // By definition, calling Flush should flush the stream, but this is
  260.             // only necessary if we passed in true for flushStream. The Web
  261.             // Services guys have some perf tests where flushing needlessly hurts.
  262.             if (flushStream)
  263.                 stream.Flush();
  264.         }
  265.        
  266.         public virtual bool AutoFlush {
  267.             get { return autoFlush; }
  268.             set {
  269.                 autoFlush = value;
  270.                 if (value)
  271.                     Flush(true, false);
  272.             }
  273.         }
  274.        
  275.         public virtual Stream BaseStream {
  276.             get { return stream; }
  277.         }
  278.        
  279.         internal bool Closable {
  280.             get { return closable; }
  281.         }
  282.         //set { closable = value; }
  283.        
  284.         internal bool HaveWrittenPreamble {
  285.             set { haveWrittenPreamble = value; }
  286.         }
  287.        
  288.         public override Encoding Encoding {
  289.             get { return encoding; }
  290.         }
  291.        
  292.         public override void Write(char value)
  293.         {
  294.             if (charPos == charLen)
  295.                 Flush(false, false);
  296.             charBuffer[charPos] = value;
  297.             charPos++;
  298.             if (autoFlush)
  299.                 Flush(true, false);
  300.         }
  301.        
  302.         public override void Write(char[] buffer)
  303.         {
  304.             // This may be faster than the one with the index & count since it
  305.             // has to do less argument checking.
  306.             if (buffer == null)
  307.                 return;
  308.             int index = 0;
  309.             int count = buffer.Length;
  310.             while (count > 0) {
  311.                 if (charPos == charLen)
  312.                     Flush(false, false);
  313.                 int n = charLen - charPos;
  314.                 if (n > count)
  315.                     n = count;
  316.                 BCLDebug.Assert(n > 0, "StreamWriter::Write(char[]) isn't making progress! This is most likely a race in user code.");
  317.                 Buffer.InternalBlockCopy(buffer, index * 2, charBuffer, charPos * 2, n * 2);
  318.                 charPos += n;
  319.                 index += n;
  320.                 count -= n;
  321.             }
  322.             if (autoFlush)
  323.                 Flush(true, false);
  324.         }
  325.        
  326.         public override void Write(char[] buffer, int index, int count)
  327.         {
  328.             if (buffer == null)
  329.                 throw new ArgumentNullException("buffer", Environment.GetResourceString("ArgumentNull_Buffer"));
  330.             if (index < 0)
  331.                 throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
  332.             if (count < 0)
  333.                 throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
  334.             if (buffer.Length - index < count)
  335.                 throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen"));
  336.            
  337.             while (count > 0) {
  338.                 if (charPos == charLen)
  339.                     Flush(false, false);
  340.                 int n = charLen - charPos;
  341.                 if (n > count)
  342.                     n = count;
  343.                 BCLDebug.Assert(n > 0, "StreamWriter::Write(char[], int, int) isn't making progress! This is most likely a race condition in user code.");
  344.                 Buffer.InternalBlockCopy(buffer, index * 2, charBuffer, charPos * 2, n * 2);
  345.                 charPos += n;
  346.                 index += n;
  347.                 count -= n;
  348.             }
  349.             if (autoFlush)
  350.                 Flush(true, false);
  351.         }
  352.        
  353.         public override void Write(string value)
  354.         {
  355.             if (value != null) {
  356.                 int count = value.Length;
  357.                 int index = 0;
  358.                 while (count > 0) {
  359.                     if (charPos == charLen)
  360.                         Flush(false, false);
  361.                     int n = charLen - charPos;
  362.                     if (n > count)
  363.                         n = count;
  364.                     BCLDebug.Assert(n > 0, "StreamWriter::Write(String) isn't making progress! This is most likely a race condition in user code.");
  365.                     value.CopyTo(index, charBuffer, charPos, n);
  366.                     charPos += n;
  367.                     index += n;
  368.                     count -= n;
  369.                 }
  370.                 if (autoFlush)
  371.                     Flush(true, false);
  372.             }
  373.         }
  374.        
  375.     }
  376. }

Developer Fusion