1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
6 using System
.Diagnostics
;
8 using System
.Threading
.Tasks
;
12 // This abstract base class represents a writer that can write
13 // primitives to an arbitrary stream. A subclass can override methods to
14 // give unique encodings.
16 public class BinaryWriter
: IDisposable
, IAsyncDisposable
18 public static readonly BinaryWriter Null
= new BinaryWriter();
20 protected Stream OutStream
;
21 private readonly byte[] _buffer
; // temp space for writing primitives to.
22 private readonly Encoding _encoding
;
23 private readonly Encoder _encoder
;
25 private bool _leaveOpen
;
27 // Perf optimization stuff
28 private byte[]? _largeByteBuffer
; // temp space for writing chars.
29 private int _maxChars
; // max # of chars we can put in _largeByteBuffer
30 // Size should be around the max number of chars/string * Encoding's max bytes/char
31 private const int LargeByteBufferSize
= 256;
33 // Protected default constructor that sets the output stream
34 // to a null stream (a bit bucket).
35 protected BinaryWriter()
37 OutStream
= Stream
.Null
;
38 _buffer
= new byte[16];
39 _encoding
= EncodingCache
.UTF8NoBOM
;
40 _encoder
= _encoding
.GetEncoder();
43 public BinaryWriter(Stream output
) : this(output
, EncodingCache
.UTF8NoBOM
, false)
47 public BinaryWriter(Stream output
, Encoding encoding
) : this(output
, encoding
, false)
51 public BinaryWriter(Stream output
, Encoding encoding
, bool leaveOpen
)
54 throw new ArgumentNullException(nameof(output
));
56 throw new ArgumentNullException(nameof(encoding
));
58 throw new ArgumentException(SR
.Argument_StreamNotWritable
);
61 _buffer
= new byte[16];
63 _encoder
= _encoding
.GetEncoder();
64 _leaveOpen
= leaveOpen
;
67 // Closes this writer and releases any system resources associated with the
68 // writer. Following a call to Close, any operations on the writer
69 // may raise exceptions.
70 public virtual void Close()
75 protected virtual void Dispose(bool disposing
)
91 public virtual ValueTask
DisposeAsync()
95 if (GetType() == typeof(BinaryWriter
))
99 return new ValueTask(OutStream
.FlushAsync());
106 // Since this is a derived BinaryWriter, delegate to whatever logic
107 // the derived implementation already has in Dispose.
113 catch (Exception exc
)
115 return new ValueTask(Task
.FromException(exc
));
119 // Returns the stream associated with the writer. It flushes all pending
120 // writes before returning. All subclasses should override Flush to
121 // ensure that all buffered data is sent to the stream.
122 public virtual Stream BaseStream
131 // Clears all buffers for this writer and causes any buffered data to be
132 // written to the underlying device.
133 public virtual void Flush()
138 public virtual long Seek(int offset
, SeekOrigin origin
)
140 return OutStream
.Seek(offset
, origin
);
143 // Writes a boolean to this stream. A single byte is written to the stream
144 // with the value 0 representing false or the value 1 representing true.
146 public virtual void Write(bool value)
148 _buffer
[0] = (byte)(value ? 1 : 0);
149 OutStream
.Write(_buffer
, 0, 1);
152 // Writes a byte to this stream. The current position of the stream is
155 public virtual void Write(byte value)
157 OutStream
.WriteByte(value);
160 // Writes a signed byte to this stream. The current position of the stream
161 // is advanced by one.
163 [CLSCompliant(false)]
164 public virtual void Write(sbyte value)
166 OutStream
.WriteByte((byte)value);
169 // Writes a byte array to this stream.
171 // This default implementation calls the Write(Object, int, int)
172 // method to write the byte array.
174 public virtual void Write(byte[] buffer
)
177 throw new ArgumentNullException(nameof(buffer
));
178 OutStream
.Write(buffer
, 0, buffer
.Length
);
181 // Writes a section of a byte array to this stream.
183 // This default implementation calls the Write(Object, int, int)
184 // method to write the byte array.
186 public virtual void Write(byte[] buffer
, int index
, int count
)
188 OutStream
.Write(buffer
, index
, count
);
192 // Writes a character to this stream. The current position of the stream is
194 // Note this method cannot handle surrogates properly in UTF-8.
196 public virtual unsafe void Write(char ch
)
198 if (char.IsSurrogate(ch
))
199 throw new ArgumentException(SR
.Arg_SurrogatesNotAllowedAsSingleChar
);
201 Debug
.Assert(_encoding
.GetMaxByteCount(1) <= 16, "_encoding.GetMaxByteCount(1) <= 16)");
203 fixed (byte* pBytes
= &_buffer
[0])
205 numBytes
= _encoder
.GetBytes(&ch
, 1, pBytes
, _buffer
.Length
, flush
: true);
207 OutStream
.Write(_buffer
, 0, numBytes
);
210 // Writes a character array to this stream.
212 // This default implementation calls the Write(Object, int, int)
213 // method to write the character array.
215 public virtual void Write(char[] chars
)
218 throw new ArgumentNullException(nameof(chars
));
220 byte[] bytes
= _encoding
.GetBytes(chars
, 0, chars
.Length
);
221 OutStream
.Write(bytes
, 0, bytes
.Length
);
224 // Writes a section of a character array to this stream.
226 // This default implementation calls the Write(Object, int, int)
227 // method to write the character array.
229 public virtual void Write(char[] chars
, int index
, int count
)
231 byte[] bytes
= _encoding
.GetBytes(chars
, index
, count
);
232 OutStream
.Write(bytes
, 0, bytes
.Length
);
236 // Writes a double to this stream. The current position of the stream is
237 // advanced by eight.
239 public virtual unsafe void Write(double value)
241 ulong TmpValue
= *(ulong*)&value;
242 _buffer
[0] = (byte)TmpValue
;
243 _buffer
[1] = (byte)(TmpValue
>> 8);
244 _buffer
[2] = (byte)(TmpValue
>> 16);
245 _buffer
[3] = (byte)(TmpValue
>> 24);
246 _buffer
[4] = (byte)(TmpValue
>> 32);
247 _buffer
[5] = (byte)(TmpValue
>> 40);
248 _buffer
[6] = (byte)(TmpValue
>> 48);
249 _buffer
[7] = (byte)(TmpValue
>> 56);
250 OutStream
.Write(_buffer
, 0, 8);
253 public virtual void Write(decimal value)
255 decimal.GetBytes(value, _buffer
);
256 OutStream
.Write(_buffer
, 0, 16);
259 // Writes a two-byte signed integer to this stream. The current position of
260 // the stream is advanced by two.
262 public virtual void Write(short value)
264 _buffer
[0] = (byte)value;
265 _buffer
[1] = (byte)(value >> 8);
266 OutStream
.Write(_buffer
, 0, 2);
269 // Writes a two-byte unsigned integer to this stream. The current position
270 // of the stream is advanced by two.
272 [CLSCompliant(false)]
273 public virtual void Write(ushort value)
275 _buffer
[0] = (byte)value;
276 _buffer
[1] = (byte)(value >> 8);
277 OutStream
.Write(_buffer
, 0, 2);
280 // Writes a four-byte signed integer to this stream. The current position
281 // of the stream is advanced by four.
283 public virtual void Write(int value)
285 _buffer
[0] = (byte)value;
286 _buffer
[1] = (byte)(value >> 8);
287 _buffer
[2] = (byte)(value >> 16);
288 _buffer
[3] = (byte)(value >> 24);
289 OutStream
.Write(_buffer
, 0, 4);
292 // Writes a four-byte unsigned integer to this stream. The current position
293 // of the stream is advanced by four.
295 [CLSCompliant(false)]
296 public virtual void Write(uint value)
298 _buffer
[0] = (byte)value;
299 _buffer
[1] = (byte)(value >> 8);
300 _buffer
[2] = (byte)(value >> 16);
301 _buffer
[3] = (byte)(value >> 24);
302 OutStream
.Write(_buffer
, 0, 4);
305 // Writes an eight-byte signed integer to this stream. The current position
306 // of the stream is advanced by eight.
308 public virtual void Write(long value)
310 _buffer
[0] = (byte)value;
311 _buffer
[1] = (byte)(value >> 8);
312 _buffer
[2] = (byte)(value >> 16);
313 _buffer
[3] = (byte)(value >> 24);
314 _buffer
[4] = (byte)(value >> 32);
315 _buffer
[5] = (byte)(value >> 40);
316 _buffer
[6] = (byte)(value >> 48);
317 _buffer
[7] = (byte)(value >> 56);
318 OutStream
.Write(_buffer
, 0, 8);
321 // Writes an eight-byte unsigned integer to this stream. The current
322 // position of the stream is advanced by eight.
324 [CLSCompliant(false)]
325 public virtual void Write(ulong value)
327 _buffer
[0] = (byte)value;
328 _buffer
[1] = (byte)(value >> 8);
329 _buffer
[2] = (byte)(value >> 16);
330 _buffer
[3] = (byte)(value >> 24);
331 _buffer
[4] = (byte)(value >> 32);
332 _buffer
[5] = (byte)(value >> 40);
333 _buffer
[6] = (byte)(value >> 48);
334 _buffer
[7] = (byte)(value >> 56);
335 OutStream
.Write(_buffer
, 0, 8);
338 // Writes a float to this stream. The current position of the stream is
341 public virtual unsafe void Write(float value)
343 uint TmpValue
= *(uint*)&value;
344 _buffer
[0] = (byte)TmpValue
;
345 _buffer
[1] = (byte)(TmpValue
>> 8);
346 _buffer
[2] = (byte)(TmpValue
>> 16);
347 _buffer
[3] = (byte)(TmpValue
>> 24);
348 OutStream
.Write(_buffer
, 0, 4);
352 // Writes a length-prefixed string to this stream in the BinaryWriter's
353 // current Encoding. This method first writes the length of the string as
354 // a four-byte unsigned integer, and then writes that many characters
357 public virtual unsafe void Write(string value)
360 throw new ArgumentNullException(nameof(value));
362 int len
= _encoding
.GetByteCount(value);
363 Write7BitEncodedInt(len
);
365 if (_largeByteBuffer
== null)
367 _largeByteBuffer
= new byte[LargeByteBufferSize
];
368 _maxChars
= _largeByteBuffer
.Length
/ _encoding
.GetMaxByteCount(1);
371 if (len
<= _largeByteBuffer
.Length
)
373 //Debug.Assert(len == _encoding.GetBytes(chars, 0, chars.Length, _largeByteBuffer, 0), "encoding's GetByteCount & GetBytes gave different answers! encoding type: "+_encoding.GetType().Name);
374 _encoding
.GetBytes(value, 0, value.Length
, _largeByteBuffer
, 0);
375 OutStream
.Write(_largeByteBuffer
, 0, len
);
379 // Aggressively try to not allocate memory in this loop for
380 // runtime performance reasons. Use an Encoder to write out
381 // the string correctly (handling surrogates crossing buffer
382 // boundaries properly).
384 int numLeft
= value.Length
;
390 // Figure out how many chars to process this round.
391 int charCount
= (numLeft
> _maxChars
) ? _maxChars
: numLeft
;
396 if (charStart
< 0 || charCount
< 0 || charStart
> value.Length
- charCount
)
398 throw new ArgumentOutOfRangeException(nameof(charCount
));
400 fixed (char* pChars
= value)
402 fixed (byte* pBytes
= &_largeByteBuffer
[0])
404 byteLen
= _encoder
.GetBytes(pChars
+ charStart
, charCount
, pBytes
, _largeByteBuffer
.Length
, charCount
== numLeft
);
409 totalBytes
+= byteLen
;
410 Debug
.Assert(totalBytes
<= len
&& byteLen
<= _largeByteBuffer
.Length
, "BinaryWriter::Write(String) - More bytes encoded than expected!");
412 OutStream
.Write(_largeByteBuffer
, 0, byteLen
);
413 charStart
+= charCount
;
414 numLeft
-= charCount
;
417 Debug
.Assert(totalBytes
== len
, "BinaryWriter::Write(String) - Didn't write out all the bytes!");
422 public virtual void Write(ReadOnlySpan
<byte> buffer
)
424 if (GetType() == typeof(BinaryWriter
))
426 OutStream
.Write(buffer
);
430 byte[] array
= ArrayPool
<byte>.Shared
.Rent(buffer
.Length
);
433 buffer
.CopyTo(array
);
434 Write(array
, 0, buffer
.Length
);
438 ArrayPool
<byte>.Shared
.Return(array
);
443 public virtual void Write(ReadOnlySpan
<char> chars
)
445 byte[] bytes
= ArrayPool
<byte>.Shared
.Rent(_encoding
.GetMaxByteCount(chars
.Length
));
448 int bytesWritten
= _encoding
.GetBytes(chars
, bytes
);
449 Write(bytes
, 0, bytesWritten
);
453 ArrayPool
<byte>.Shared
.Return(bytes
);
457 protected void Write7BitEncodedInt(int value)
459 // Write out an int 7 bits at a time. The high bit of the byte,
460 // when on, tells reader to continue reading more bytes.
461 uint v
= (uint)value; // support negative numbers
464 Write((byte)(v
| 0x80));