2008-03-05 09:52:00 -05:00
// ==++==
/ /
// Copyright (c) Microsoft Corporation. All rights reserved.
/ /
// ==--==
2008-11-17 18:29:00 -05:00
//============================================================
/ /
// Class: StreamWriter
/ /
// Purpose: For writing text to streams in a particular
// encoding.
/ /
//===========================================================
2008-03-05 09:52:00 -05:00
using System ;
using System.Diagnostics ;
using System.Text ;
2008-11-17 18:29:00 -05:00
namespace System.IO
{
2008-03-05 09:52:00 -05:00
// This class implements a TextWriter for writing characters to a Stream.
// This is designed for character output in a particular Encoding,
// whereas the Stream class is designed for byte input and output.
/ /
//| <include file='doc\StreamWriter.uex' path='docs/doc[@for="StreamWriter"]/*' />
public class StreamWriter : TextWriter
{
// For UTF-8, the values of 1K for the default buffer size and 4K for the
// file stream buffer size are reasonable & give very reasonable
// performance for in terms of construction time for the StreamWriter and
// write perf. Note that for UTF-8, we end up allocating a 4K byte buffer,
2008-11-17 18:29:00 -05:00
// which means we take advantage of the adaptive buffering code.
// The performance using UnicodeEncoding is acceptable.
2008-03-05 09:52:00 -05:00
private const int DefaultBufferSize = 1024 ; // char[]
private const int DefaultFileStreamBufferSize = 4096 ;
private const int MinBufferSize = 128 ;
#if DONT
// Bit bucket - Null has no backing store.
//| <include file='doc\StreamWriter.uex' path='docs/doc[@for="StreamWriter.Null"]/*' />
public new static readonly StreamWriter Null = new StreamWriter ( Stream . Null , new UTF8Encoding ( false , true ) , 128 ) ;
#endif
internal Stream stream ;
private Encoding encoding ;
private Encoder encoder ;
internal byte [ ] byteBuffer ;
internal char [ ] charBuffer ;
internal int charPos ;
internal int charLen ;
internal bool autoFlush ;
private bool haveWrittenPreamble ;
private bool closable ; // For Console.Out - should Finalize call Dispose?
#if _DEBUG
private String allocatedFrom = String . Empty ;
#endif
[Microsoft.Contracts.NotDelayed]
internal StreamWriter ( ) {
}
//| <include file='doc\StreamWriter.uex' path='docs/doc[@for="StreamWriter.StreamWriter"]/*' />
[Microsoft.Contracts.NotDelayed]
public StreamWriter ( Stream stream )
: this ( stream , new UTF8Encoding ( false , true ) , DefaultBufferSize ) {
}
//| <include file='doc\StreamWriter.uex' path='docs/doc[@for="StreamWriter.StreamWriter1"]/*' />
[Microsoft.Contracts.NotDelayed]
public StreamWriter ( Stream stream , Encoding encoding )
: this ( stream , encoding , DefaultBufferSize ) {
}
// Creates a new StreamWriter for the given stream. The
// character encoding is set by encoding and the buffer size,
// in number of 16-bit characters, is set by bufferSize.
/ /
//| <include file='doc\StreamWriter.uex' path='docs/doc[@for="StreamWriter.StreamWriter2"]/*' />
[Microsoft.Contracts.NotDelayed]
public StreamWriter ( Stream stream , Encoding encoding , int bufferSize ) {
2008-11-17 18:29:00 -05:00
if ( stream = = null | | encoding = = null )
2008-03-05 09:52:00 -05:00
throw new ArgumentNullException ( ( stream = = null ? "stream" : "encoding" ) ) ;
if ( ! stream . CanWrite )
throw new ArgumentException ( "Argument_StreamNotWritable" ) ;
if ( bufferSize < = 0 ) throw new ArgumentOutOfRangeException ( "ArgumentOutOfRange_NeedPosNum" ) ;
Init ( stream , encoding , bufferSize ) ;
}
//| <include file='doc\StreamWriter.uex' path='docs/doc[@for="StreamWriter.StreamWriter3"]/*' />
[Microsoft.Contracts.NotDelayed]
public StreamWriter ( String path )
: this ( path , false , new UTF8Encoding ( false , true ) , DefaultBufferSize ) {
}
//| <include file='doc\StreamWriter.uex' path='docs/doc[@for="StreamWriter.StreamWriter4"]/*' />
[Microsoft.Contracts.NotDelayed]
public StreamWriter ( String path , bool append )
: this ( path , append , new UTF8Encoding ( false , true ) , DefaultBufferSize ) {
}
//| <include file='doc\StreamWriter.uex' path='docs/doc[@for="StreamWriter.StreamWriter5"]/*' />
[Microsoft.Contracts.NotDelayed]
public StreamWriter ( String path , bool append , Encoding encoding )
: this ( path , append , encoding , DefaultBufferSize ) {
}
//| <include file='doc\StreamWriter.uex' path='docs/doc[@for="StreamWriter.StreamWriter6"]/*' />
[Microsoft.Contracts.NotDelayed]
public StreamWriter ( String path , bool append , Encoding encoding , int bufferSize ) {
2008-11-17 18:29:00 -05:00
if ( path = = null | | encoding = = null )
2008-03-05 09:52:00 -05:00
throw new ArgumentNullException ( ( path = = null ? "path" : "encoding" ) ) ;
if ( bufferSize < = 0 ) throw new ArgumentOutOfRangeException ( "ArgumentOutOfRange_NeedPosNum" ) ;
Stream stream = CreateFile ( path , append ) ;
Init ( stream , encoding , bufferSize ) ;
}
private void Init ( Stream ! stream , Encoding ! encoding , int bufferSize )
{
this . stream = stream ;
this . encoding = encoding ;
this . encoder = encoding . GetEncoder ( ) ;
if ( bufferSize < MinBufferSize ) bufferSize = MinBufferSize ;
charBuffer = new char [ bufferSize ] ;
byteBuffer = new byte [ encoding . GetMaxByteCount ( bufferSize ) ] ;
charLen = bufferSize ;
// If we're appending to a Stream that already has data, don't write
// the preamble junk.
if ( stream . CanSeek & & stream . Position > 0 )
haveWrittenPreamble = true ;
closable = true ;
}
private static Stream ! CreateFile ( String path , bool append ) {
FileMode mode = append ? FileMode . Append : FileMode . Create ;
FileStream f = new FileStream ( path , mode , FileAccess . Write , FileShare . Read , DefaultFileStreamBufferSize ) ;
return f ;
}
//| <include file='doc\StreamWriter.uex' path='docs/doc[@for="StreamWriter.Close"]/*' />
public override void Close ( ) {
Dispose ( true ) ;
}
//| <include file='doc\StreamWriter.uex' path='docs/doc[@for="StreamWriter.Dispose"]/*' />
protected override void Dispose ( bool disposing ) {
// Dispose of our resources if this StreamWriter is closable.
// Note that Console.Out should not be closable.
if ( disposing ) {
if ( stream ! = null ) {
Flush ( true , true ) ;
if ( Closable )
stream . Close ( ) ;
}
}
if ( Closable & & stream ! = null ) {
stream = null ;
byteBuffer = null ;
charBuffer = null ;
encoding = null ;
encoder = null ;
charLen = 0 ;
base . Dispose ( disposing ) ;
}
}
//| <include file='doc\StreamWriter.uex' path='docs/doc[@for="StreamWriter.Finalize"]/*' />
~ StreamWriter ( )
{
// Make sure people closed this StreamWriter, but of course, make
// allowances for BufferedStream (used by Console.Out & Error) and
// StreamWriter::Null.
#if _DEBUG
Debug . Correctness ( charPos = = 0 , "You didn't close a StreamWriter, and you lost data!\r\nStream type: " + ( stream = = null ? "<null>" : stream . GetType ( ) . FullName ) + ( ( stream ! = null & & stream is FileStream ) ? " File name: " + ( ( FileStream ) stream ) . NameInternal : "" ) + "\r\nAllocated from:\r\n" + allocatedFrom ) ;
#endif
Dispose ( false ) ;
}
//| <include file='doc\StreamWriter.uex' path='docs/doc[@for="StreamWriter.Flush"]/*' />
public override void Flush ( ) {
Flush ( true , true ) ;
}
private void Flush ( bool flushStream , bool flushEncoder ) {
// flushEncoder should be true at the end of the file and if
// the user explicitly calls Flush (though not if AutoFlush is true).
// This is required to flush any dangling characters from our UTF-7
// and UTF-8 encoders.
if ( stream = = null )
__Error . WriterClosed ( ) ;
// Perf boost for Flush on non-dirty writers.
2008-11-17 18:29:00 -05:00
if ( charPos = = 0 & & ! flushStream & & ! flushEncoder )
2008-03-05 09:52:00 -05:00
return ;
if ( ! haveWrittenPreamble ) {
haveWrittenPreamble = true ;
byte [ ] preamble = encoding . GetPreamble ( ) ;
if ( preamble . Length > 0 )
stream . Write ( preamble , 0 , preamble . Length ) ;
}
int count = encoder . GetBytes ( charBuffer , 0 , charPos , byteBuffer , 0 , flushEncoder ) ;
charPos = 0 ;
if ( count > 0 )
stream . Write ( byteBuffer , 0 , count ) ;
// By definition, calling Flush should flush the stream, but this is
// only necessary if we passed in true for flushStream. The Web
// Services guys have some perf tests where flushing needlessly hurts.
if ( flushStream )
stream . Flush ( ) ;
}
//| <include file='doc\StreamWriter.uex' path='docs/doc[@for="StreamWriter.AutoFlush"]/*' />
public virtual bool AutoFlush {
get { return autoFlush ; }
set {
autoFlush = value ;
if ( value ) Flush ( true , false ) ;
}
}
//| <include file='doc\StreamWriter.uex' path='docs/doc[@for="StreamWriter.BaseStream"]/*' />
public virtual Stream BaseStream {
get { return stream ; }
}
internal bool Closable {
get { return closable ; }
set { closable = value ; }
}
//| <include file='doc\StreamWriter.uex' path='docs/doc[@for="StreamWriter.Encoding"]/*' />
public override Encoding Encoding {
get { return encoding ; }
}
//| <include file='doc\StreamWriter.uex' path='docs/doc[@for="StreamWriter.Write"]/*' />
public override void Write ( char value ) {
if ( charPos = = charLen ) Flush ( false , false ) ;
charBuffer [ charPos + + ] = value ;
if ( autoFlush ) Flush ( true , false ) ;
}
//| <include file='doc\StreamWriter.uex' path='docs/doc[@for="StreamWriter.Write1"]/*' />
public override void Write ( char [ ] buffer ) {
if ( buffer = = null ) {
return ;
}
int index = 0 ;
int count = buffer . Length ;
while ( count > 0 ) {
int limit = this . charLen - this . charPos ;
if ( limit < = 0 ) {
this . Flush ( false , false ) ;
limit = this . charLen - this . charPos ;
}
if ( limit > count ) {
limit = count ;
}
Buffer . BlockCopy ( buffer , index * 2 ,
charBuffer , charPos * 2 ,
limit * 2 ) ;
this . charPos + = limit ;
index + = limit ;
count - = limit ;
}
if ( this . autoFlush ) {
this . Flush ( true , false ) ;
}
}
//| <include file='doc\StreamWriter.uex' path='docs/doc[@for="StreamWriter.Write2"]/*' />
public override void Write ( char [ ] buffer , int index , int count ) {
if ( buffer = = null ) {
throw new ArgumentNullException ( "buffer" , "Buffer is null" ) ;
}
if ( index < 0 ) {
throw new ArgumentOutOfRangeException ( "index" , "index is negative" ) ;
}
if ( count < 0 ) {
throw new ArgumentOutOfRangeException ( "count" , "count is negative" ) ;
}
if ( buffer . Length < index + count ) {
throw new ArgumentException ( "index + count greater than buffer length" ) ;
}
while ( count > 0 ) {
int limit = this . charLen - this . charPos ;
if ( limit < = 0 ) {
this . Flush ( false , false ) ;
limit = this . charLen - this . charPos ;
}
if ( limit > count ) {
limit = count ;
}
for ( int i = 0 ; i < limit ; i + + ) {
charBuffer [ this . charPos + i ] = buffer [ index + i ] ;
}
this . charPos + = limit ;
index + = limit ;
count - = limit ;
}
if ( this . autoFlush ) {
this . Flush ( true , false ) ;
}
}
//| <include file='doc\StreamWriter.uex' path='docs/doc[@for="StreamWriter.Write3"]/*' />
public override void Write ( String value ) {
if ( value ! = null ) {
int count = value . Length ;
int index = 0 ;
while ( count > 0 ) {
if ( charPos = = charLen ) Flush ( false , false ) ;
int n = charLen - charPos ;
if ( n > count ) n = count ;
Debug . Assert ( n > 0 , "StreamWriter::Write(String) isn't making progress! This is most likely a race condition in user code." ) ;
value . CopyTo ( index , charBuffer , charPos , n ) ;
charPos + = n ;
index + = n ;
count - = n ;
}
if ( autoFlush ) Flush ( true , false ) ;
}
}
2008-11-17 18:29:00 -05:00
/ /
//// This method is more efficient for long strings outputted to streams
//// than the one on TextWriter, and won't cause any problems in terms of
//// hiding methods on TextWriter as long as languages respect the
//// hide-by-name-and-sig metadata flag.
//public override void WriteLine(String value) {
//if (value != null) {
//int count = value.Length;
//int index = 0;
//while (count > 0) {
//if (charPos == charLen) Flush(false);
//int n = charLen - charPos;
//if (n > count) n = count;
//Debug.Assert(n > 0, "StreamWriter::WriteLine(String) isn't making progress! This is most likely a race condition in user code.");
//value.CopyTo(index, charBuffer, charPos, n);
//charPos += n;
//index += n;
//count -= n;
//}
//}
//if (charPos >= charLen - 2) Flush(false);
//Buffer.BlockCopy(CoreNewLine, 0, charBuffer, charPos*2, CoreNewLine.Length * 2);
//charPos += CoreNewLine.Length;
//if (autoFlush) Flush(true, false);
//}
/ /
2008-03-05 09:52:00 -05:00
}
}