StreamWriter.cs 41 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084
  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.
  4. using System.Diagnostics;
  5. using System.Diagnostics.CodeAnalysis;
  6. using System.Runtime.CompilerServices;
  7. using System.Runtime.InteropServices;
  8. using System.Text;
  9. using System.Threading;
  10. using System.Threading.Tasks;
  11. namespace System.IO
  12. {
  13. // This class implements a TextWriter for writing characters to a Stream.
  14. // This is designed for character output in a particular Encoding,
  15. // whereas the Stream class is designed for byte input and output.
  16. public class StreamWriter : TextWriter
  17. {
  18. // For UTF-8, the values of 1K for the default buffer size and 4K for the
  19. // file stream buffer size are reasonable & give very reasonable
  20. // performance for in terms of construction time for the StreamWriter and
  21. // write perf. Note that for UTF-8, we end up allocating a 4K byte buffer,
  22. // which means we take advantage of adaptive buffering code.
  23. // The performance using UnicodeEncoding is acceptable.
  24. private const int DefaultBufferSize = 1024; // char[]
  25. private const int DefaultFileStreamBufferSize = 4096;
  26. private const int MinBufferSize = 128;
  27. // Bit bucket - Null has no backing store. Non closable.
  28. public static new readonly StreamWriter Null = new StreamWriter(Stream.Null, UTF8NoBOM, MinBufferSize, leaveOpen: true);
  29. private readonly Stream _stream;
  30. private readonly Encoding _encoding;
  31. private readonly Encoder _encoder;
  32. private readonly byte[] _byteBuffer;
  33. private readonly char[] _charBuffer;
  34. private int _charPos;
  35. private int _charLen;
  36. private bool _autoFlush;
  37. private bool _haveWrittenPreamble;
  38. private readonly bool _closable;
  39. private bool _disposed;
  40. // We don't guarantee thread safety on StreamWriter, but we should at
  41. // least prevent users from trying to write anything while an Async
  42. // write from the same thread is in progress.
  43. private Task _asyncWriteTask = Task.CompletedTask;
  44. private void CheckAsyncTaskInProgress()
  45. {
  46. // We are not locking the access to _asyncWriteTask because this is not meant to guarantee thread safety.
  47. // We are simply trying to deter calling any Write APIs while an async Write from the same thread is in progress.
  48. if (!_asyncWriteTask.IsCompleted)
  49. {
  50. ThrowAsyncIOInProgress();
  51. }
  52. }
  53. [DoesNotReturn]
  54. private static void ThrowAsyncIOInProgress() =>
  55. throw new InvalidOperationException(SR.InvalidOperation_AsyncIOInProgress);
  56. // The high level goal is to be tolerant of encoding errors when we read and very strict
  57. // when we write. Hence, default StreamWriter encoding will throw on encoding error.
  58. // Note: when StreamWriter throws on invalid encoding chars (for ex, high surrogate character
  59. // D800-DBFF without a following low surrogate character DC00-DFFF), it will cause the
  60. // internal StreamWriter's state to be irrecoverable as it would have buffered the
  61. // illegal chars and any subsequent call to Flush() would hit the encoding error again.
  62. // Even Close() will hit the exception as it would try to flush the unwritten data.
  63. // Maybe we can add a DiscardBufferedData() method to get out of such situation (like
  64. // StreamReader though for different reason). Either way, the buffered data will be lost!
  65. private static Encoding UTF8NoBOM => EncodingCache.UTF8NoBOM;
  66. public StreamWriter(Stream stream)
  67. : this(stream, UTF8NoBOM, DefaultBufferSize, false)
  68. {
  69. }
  70. public StreamWriter(Stream stream, Encoding encoding)
  71. : this(stream, encoding, DefaultBufferSize, false)
  72. {
  73. }
  74. // Creates a new StreamWriter for the given stream. The
  75. // character encoding is set by encoding and the buffer size,
  76. // in number of 16-bit characters, is set by bufferSize.
  77. //
  78. public StreamWriter(Stream stream, Encoding encoding, int bufferSize)
  79. : this(stream, encoding, bufferSize, false)
  80. {
  81. }
  82. public StreamWriter(Stream stream, Encoding? encoding = null, int bufferSize = -1, bool leaveOpen = false)
  83. : base(null) // Ask for CurrentCulture all the time
  84. {
  85. if (stream == null)
  86. {
  87. throw new ArgumentNullException(nameof(stream));
  88. }
  89. if (encoding == null)
  90. {
  91. encoding = UTF8NoBOM;
  92. }
  93. if (!stream.CanWrite)
  94. {
  95. throw new ArgumentException(SR.Argument_StreamNotWritable);
  96. }
  97. if (bufferSize == -1)
  98. {
  99. bufferSize = DefaultBufferSize;
  100. }
  101. else if (bufferSize <= 0)
  102. {
  103. throw new ArgumentOutOfRangeException(nameof(bufferSize), SR.ArgumentOutOfRange_NeedPosNum);
  104. }
  105. _stream = stream;
  106. _encoding = encoding;
  107. _encoder = _encoding.GetEncoder();
  108. if (bufferSize < MinBufferSize)
  109. {
  110. bufferSize = MinBufferSize;
  111. }
  112. _charBuffer = new char[bufferSize];
  113. _byteBuffer = new byte[_encoding.GetMaxByteCount(bufferSize)];
  114. _charLen = bufferSize;
  115. // If we're appending to a Stream that already has data, don't write
  116. // the preamble.
  117. if (_stream.CanSeek && _stream.Position > 0)
  118. {
  119. _haveWrittenPreamble = true;
  120. }
  121. _closable = !leaveOpen;
  122. }
  123. public StreamWriter(string path)
  124. : this(path, false, UTF8NoBOM, DefaultBufferSize)
  125. {
  126. }
  127. public StreamWriter(string path, bool append)
  128. : this(path, append, UTF8NoBOM, DefaultBufferSize)
  129. {
  130. }
  131. public StreamWriter(string path, bool append, Encoding encoding)
  132. : this(path, append, encoding, DefaultBufferSize)
  133. {
  134. }
  135. public StreamWriter(string path, bool append, Encoding encoding, int bufferSize) :
  136. this(ValidateArgsAndOpenPath(path, append, encoding, bufferSize), encoding, bufferSize, leaveOpen: false)
  137. {
  138. }
  139. private static Stream ValidateArgsAndOpenPath(string path, bool append, Encoding encoding, int bufferSize)
  140. {
  141. if (path == null)
  142. throw new ArgumentNullException(nameof(path));
  143. if (encoding == null)
  144. throw new ArgumentNullException(nameof(encoding));
  145. if (path.Length == 0)
  146. throw new ArgumentException(SR.Argument_EmptyPath);
  147. if (bufferSize <= 0)
  148. throw new ArgumentOutOfRangeException(nameof(bufferSize), SR.ArgumentOutOfRange_NeedPosNum);
  149. return new FileStream(path, append ? FileMode.Append : FileMode.Create, FileAccess.Write, FileShare.Read, DefaultFileStreamBufferSize, FileOptions.SequentialScan);
  150. }
  151. public override void Close()
  152. {
  153. Dispose(true);
  154. GC.SuppressFinalize(this);
  155. }
  156. protected override void Dispose(bool disposing)
  157. {
  158. try
  159. {
  160. // We need to flush any buffered data if we are being closed/disposed.
  161. // Also, we never close the handles for stdout & friends. So we can safely
  162. // write any buffered data to those streams even during finalization, which
  163. // is generally the right thing to do.
  164. if (!_disposed && disposing)
  165. {
  166. // Note: flush on the underlying stream can throw (ex., low disk space)
  167. CheckAsyncTaskInProgress();
  168. Flush(flushStream: true, flushEncoder: true);
  169. }
  170. }
  171. finally
  172. {
  173. CloseStreamFromDispose(disposing);
  174. }
  175. }
  176. private void CloseStreamFromDispose(bool disposing)
  177. {
  178. // Dispose of our resources if this StreamWriter is closable.
  179. if (_closable && !_disposed)
  180. {
  181. try
  182. {
  183. // Attempt to close the stream even if there was an IO error from Flushing.
  184. // Note that Stream.Close() can potentially throw here (may or may not be
  185. // due to the same Flush error). In this case, we still need to ensure
  186. // cleaning up internal resources, hence the finally block.
  187. if (disposing)
  188. {
  189. _stream.Close();
  190. }
  191. }
  192. finally
  193. {
  194. _disposed = true;
  195. _charLen = 0;
  196. base.Dispose(disposing);
  197. }
  198. }
  199. }
  200. public override ValueTask DisposeAsync() =>
  201. GetType() != typeof(StreamWriter) ?
  202. base.DisposeAsync() :
  203. DisposeAsyncCore();
  204. private async ValueTask DisposeAsyncCore()
  205. {
  206. // Same logic as in Dispose(), but with async flushing.
  207. Debug.Assert(GetType() == typeof(StreamWriter));
  208. try
  209. {
  210. if (!_disposed)
  211. {
  212. await FlushAsync().ConfigureAwait(false);
  213. }
  214. }
  215. finally
  216. {
  217. CloseStreamFromDispose(disposing: true);
  218. }
  219. GC.SuppressFinalize(this);
  220. }
  221. public override void Flush()
  222. {
  223. CheckAsyncTaskInProgress();
  224. Flush(true, true);
  225. }
  226. private void Flush(bool flushStream, bool flushEncoder)
  227. {
  228. // flushEncoder should be true at the end of the file and if
  229. // the user explicitly calls Flush (though not if AutoFlush is true).
  230. // This is required to flush any dangling characters from our UTF-7
  231. // and UTF-8 encoders.
  232. ThrowIfDisposed();
  233. // Perf boost for Flush on non-dirty writers.
  234. if (_charPos == 0 && !flushStream && !flushEncoder)
  235. {
  236. return;
  237. }
  238. if (!_haveWrittenPreamble)
  239. {
  240. _haveWrittenPreamble = true;
  241. ReadOnlySpan<byte> preamble = _encoding.Preamble;
  242. if (preamble.Length > 0)
  243. {
  244. _stream.Write(preamble);
  245. }
  246. }
  247. int count = _encoder.GetBytes(_charBuffer, 0, _charPos, _byteBuffer, 0, flushEncoder);
  248. _charPos = 0;
  249. if (count > 0)
  250. {
  251. _stream.Write(_byteBuffer, 0, count);
  252. }
  253. // By definition, calling Flush should flush the stream, but this is
  254. // only necessary if we passed in true for flushStream. The Web
  255. // Services guys have some perf tests where flushing needlessly hurts.
  256. if (flushStream)
  257. {
  258. _stream.Flush();
  259. }
  260. }
  261. public virtual bool AutoFlush
  262. {
  263. get { return _autoFlush; }
  264. set
  265. {
  266. CheckAsyncTaskInProgress();
  267. _autoFlush = value;
  268. if (value)
  269. {
  270. Flush(true, false);
  271. }
  272. }
  273. }
  274. public virtual Stream BaseStream
  275. {
  276. get { return _stream; }
  277. }
  278. public override Encoding Encoding
  279. {
  280. get { return _encoding; }
  281. }
  282. public override void Write(char value)
  283. {
  284. CheckAsyncTaskInProgress();
  285. if (_charPos == _charLen)
  286. {
  287. Flush(false, false);
  288. }
  289. _charBuffer[_charPos] = value;
  290. _charPos++;
  291. if (_autoFlush)
  292. {
  293. Flush(true, false);
  294. }
  295. }
  296. [MethodImpl(MethodImplOptions.NoInlining)] // prevent WriteSpan from bloating call sites
  297. public override void Write(char[]? buffer)
  298. {
  299. WriteSpan(buffer, appendNewLine: false);
  300. }
  301. [MethodImpl(MethodImplOptions.NoInlining)] // prevent WriteSpan from bloating call sites
  302. public override void Write(char[] buffer, int index, int count)
  303. {
  304. if (buffer == null)
  305. {
  306. throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
  307. }
  308. if (index < 0)
  309. {
  310. throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_NeedNonNegNum);
  311. }
  312. if (count < 0)
  313. {
  314. throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
  315. }
  316. if (buffer.Length - index < count)
  317. {
  318. throw new ArgumentException(SR.Argument_InvalidOffLen);
  319. }
  320. WriteSpan(buffer.AsSpan(index, count), appendNewLine: false);
  321. }
  322. [MethodImpl(MethodImplOptions.NoInlining)] // prevent WriteSpan from bloating call sites
  323. public override void Write(ReadOnlySpan<char> buffer)
  324. {
  325. if (GetType() == typeof(StreamWriter))
  326. {
  327. WriteSpan(buffer, appendNewLine: false);
  328. }
  329. else
  330. {
  331. // If a derived class may have overridden existing Write behavior,
  332. // we need to make sure we use it.
  333. base.Write(buffer);
  334. }
  335. }
  336. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  337. private unsafe void WriteSpan(ReadOnlySpan<char> buffer, bool appendNewLine)
  338. {
  339. CheckAsyncTaskInProgress();
  340. if (buffer.Length <= 4 && // Threshold of 4 chosen based on perf experimentation
  341. buffer.Length <= _charLen - _charPos)
  342. {
  343. // For very short buffers and when we don't need to worry about running out of space
  344. // in the char buffer, just copy the chars individually.
  345. for (int i = 0; i < buffer.Length; i++)
  346. {
  347. _charBuffer[_charPos++] = buffer[i];
  348. }
  349. }
  350. else
  351. {
  352. // For larger buffers or when we may run out of room in the internal char buffer, copy in chunks.
  353. // Use unsafe code until https://github.com/dotnet/coreclr/issues/13827 is addressed, as spans are
  354. // resulting in significant overhead (even when the if branch above is taken rather than this
  355. // else) due to temporaries that need to be cleared. Given the use of unsafe code, we also
  356. // make local copies of instance state to protect against potential concurrent misuse.
  357. ThrowIfDisposed();
  358. char[] charBuffer = _charBuffer;
  359. fixed (char* bufferPtr = &MemoryMarshal.GetReference(buffer))
  360. fixed (char* dstPtr = &charBuffer[0])
  361. {
  362. char* srcPtr = bufferPtr;
  363. int count = buffer.Length;
  364. int dstPos = _charPos; // use a local copy of _charPos for safety
  365. while (count > 0)
  366. {
  367. if (dstPos == charBuffer.Length)
  368. {
  369. Flush(false, false);
  370. dstPos = 0;
  371. }
  372. int n = Math.Min(charBuffer.Length - dstPos, count);
  373. int bytesToCopy = n * sizeof(char);
  374. Buffer.MemoryCopy(srcPtr, dstPtr + dstPos, bytesToCopy, bytesToCopy);
  375. _charPos += n;
  376. dstPos += n;
  377. srcPtr += n;
  378. count -= n;
  379. }
  380. }
  381. }
  382. if (appendNewLine)
  383. {
  384. char[] coreNewLine = CoreNewLine;
  385. for (int i = 0; i < coreNewLine.Length; i++) // Generally 1 (\n) or 2 (\r\n) iterations
  386. {
  387. if (_charPos == _charLen)
  388. {
  389. Flush(false, false);
  390. }
  391. _charBuffer[_charPos] = coreNewLine[i];
  392. _charPos++;
  393. }
  394. }
  395. if (_autoFlush)
  396. {
  397. Flush(true, false);
  398. }
  399. }
  400. [MethodImpl(MethodImplOptions.NoInlining)] // prevent WriteSpan from bloating call sites
  401. public override void Write(string? value)
  402. {
  403. WriteSpan(value, appendNewLine: false);
  404. }
  405. [MethodImpl(MethodImplOptions.NoInlining)] // prevent WriteSpan from bloating call sites
  406. public override void WriteLine(string? value)
  407. {
  408. CheckAsyncTaskInProgress();
  409. WriteSpan(value, appendNewLine: true);
  410. }
  411. [MethodImpl(MethodImplOptions.NoInlining)] // prevent WriteSpan from bloating call sites
  412. public override void WriteLine(ReadOnlySpan<char> value)
  413. {
  414. if (GetType() == typeof(StreamWriter))
  415. {
  416. CheckAsyncTaskInProgress();
  417. WriteSpan(value, appendNewLine: true);
  418. }
  419. else
  420. {
  421. // If a derived class may have overridden existing WriteLine behavior,
  422. // we need to make sure we use it.
  423. base.WriteLine(value);
  424. }
  425. }
  426. private void WriteFormatHelper(string format, ParamsArray args, bool appendNewLine)
  427. {
  428. StringBuilder sb =
  429. StringBuilderCache.Acquire((format?.Length ?? 0) + args.Length * 8)
  430. .AppendFormatHelper(null, format!, args); // AppendFormatHelper will appropriately throw ArgumentNullException for a null format
  431. StringBuilder.ChunkEnumerator chunks = sb.GetChunks();
  432. bool more = chunks.MoveNext();
  433. while (more)
  434. {
  435. ReadOnlySpan<char> current = chunks.Current.Span;
  436. more = chunks.MoveNext();
  437. // If final chunk, include the newline if needed
  438. WriteSpan(current, appendNewLine: more ? false : appendNewLine);
  439. }
  440. StringBuilderCache.Release(sb);
  441. }
  442. public override void Write(string format, object? arg0)
  443. {
  444. if (GetType() == typeof(StreamWriter))
  445. {
  446. WriteFormatHelper(format, new ParamsArray(arg0), appendNewLine: false);
  447. }
  448. else
  449. {
  450. base.Write(format, arg0);
  451. }
  452. }
  453. public override void Write(string format, object? arg0, object? arg1)
  454. {
  455. if (GetType() == typeof(StreamWriter))
  456. {
  457. WriteFormatHelper(format, new ParamsArray(arg0, arg1), appendNewLine: false);
  458. }
  459. else
  460. {
  461. base.Write(format, arg0, arg1);
  462. }
  463. }
  464. public override void Write(string format, object? arg0, object? arg1, object? arg2)
  465. {
  466. if (GetType() == typeof(StreamWriter))
  467. {
  468. WriteFormatHelper(format, new ParamsArray(arg0, arg1, arg2), appendNewLine: false);
  469. }
  470. else
  471. {
  472. base.Write(format, arg0, arg1, arg2);
  473. }
  474. }
  475. public override void Write(string format, params object?[] arg)
  476. {
  477. if (GetType() == typeof(StreamWriter))
  478. {
  479. if (arg == null)
  480. {
  481. throw new ArgumentNullException((format == null) ? nameof(format) : nameof(arg)); // same as base logic
  482. }
  483. WriteFormatHelper(format, new ParamsArray(arg), appendNewLine: false);
  484. }
  485. else
  486. {
  487. base.Write(format, arg);
  488. }
  489. }
  490. public override void WriteLine(string format, object? arg0)
  491. {
  492. if (GetType() == typeof(StreamWriter))
  493. {
  494. WriteFormatHelper(format, new ParamsArray(arg0), appendNewLine: true);
  495. }
  496. else
  497. {
  498. base.WriteLine(format, arg0);
  499. }
  500. }
  501. public override void WriteLine(string format, object? arg0, object? arg1)
  502. {
  503. if (GetType() == typeof(StreamWriter))
  504. {
  505. WriteFormatHelper(format, new ParamsArray(arg0, arg1), appendNewLine: true);
  506. }
  507. else
  508. {
  509. base.WriteLine(format, arg0, arg1);
  510. }
  511. }
  512. public override void WriteLine(string format, object? arg0, object? arg1, object? arg2)
  513. {
  514. if (GetType() == typeof(StreamWriter))
  515. {
  516. WriteFormatHelper(format, new ParamsArray(arg0, arg1, arg2), appendNewLine: true);
  517. }
  518. else
  519. {
  520. base.WriteLine(format, arg0, arg1, arg2);
  521. }
  522. }
  523. public override void WriteLine(string format, params object?[] arg)
  524. {
  525. if (GetType() == typeof(StreamWriter))
  526. {
  527. if (arg == null)
  528. {
  529. throw new ArgumentNullException(nameof(arg));
  530. }
  531. WriteFormatHelper(format, new ParamsArray(arg), appendNewLine: true);
  532. }
  533. else
  534. {
  535. base.WriteLine(format, arg);
  536. }
  537. }
  538. public override Task WriteAsync(char value)
  539. {
  540. // If we have been inherited into a subclass, the following implementation could be incorrect
  541. // since it does not call through to Write() which a subclass might have overridden.
  542. // To be safe we will only use this implementation in cases where we know it is safe to do so,
  543. // and delegate to our base class (which will call into Write) when we are not sure.
  544. if (GetType() != typeof(StreamWriter))
  545. {
  546. return base.WriteAsync(value);
  547. }
  548. ThrowIfDisposed();
  549. CheckAsyncTaskInProgress();
  550. Task task = WriteAsyncInternal(this, value, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: false);
  551. _asyncWriteTask = task;
  552. return task;
  553. }
  554. // We pass in private instance fields of this MarshalByRefObject-derived type as local params
  555. // to ensure performant access inside the state machine that corresponds this async method.
  556. // Fields that are written to must be assigned at the end of the method *and* before instance invocations.
  557. private static async Task WriteAsyncInternal(StreamWriter _this, char value,
  558. char[] charBuffer, int charPos, int charLen, char[] coreNewLine,
  559. bool autoFlush, bool appendNewLine)
  560. {
  561. if (charPos == charLen)
  562. {
  563. await _this.FlushAsyncInternal(false, false, charBuffer, charPos).ConfigureAwait(false);
  564. Debug.Assert(_this._charPos == 0);
  565. charPos = 0;
  566. }
  567. charBuffer[charPos] = value;
  568. charPos++;
  569. if (appendNewLine)
  570. {
  571. for (int i = 0; i < coreNewLine.Length; i++) // Expect 2 iterations, no point calling BlockCopy
  572. {
  573. if (charPos == charLen)
  574. {
  575. await _this.FlushAsyncInternal(false, false, charBuffer, charPos).ConfigureAwait(false);
  576. Debug.Assert(_this._charPos == 0);
  577. charPos = 0;
  578. }
  579. charBuffer[charPos] = coreNewLine[i];
  580. charPos++;
  581. }
  582. }
  583. if (autoFlush)
  584. {
  585. await _this.FlushAsyncInternal(true, false, charBuffer, charPos).ConfigureAwait(false);
  586. Debug.Assert(_this._charPos == 0);
  587. charPos = 0;
  588. }
  589. _this._charPos = charPos;
  590. }
  591. public override Task WriteAsync(string? value)
  592. {
  593. // If we have been inherited into a subclass, the following implementation could be incorrect
  594. // since it does not call through to Write() which a subclass might have overridden.
  595. // To be safe we will only use this implementation in cases where we know it is safe to do so,
  596. // and delegate to our base class (which will call into Write) when we are not sure.
  597. if (GetType() != typeof(StreamWriter))
  598. {
  599. return base.WriteAsync(value);
  600. }
  601. if (value != null)
  602. {
  603. ThrowIfDisposed();
  604. CheckAsyncTaskInProgress();
  605. Task task = WriteAsyncInternal(this, value, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: false);
  606. _asyncWriteTask = task;
  607. return task;
  608. }
  609. else
  610. {
  611. return Task.CompletedTask;
  612. }
  613. }
  614. // We pass in private instance fields of this MarshalByRefObject-derived type as local params
  615. // to ensure performant access inside the state machine that corresponds this async method.
  616. // Fields that are written to must be assigned at the end of the method *and* before instance invocations.
  617. private static async Task WriteAsyncInternal(StreamWriter _this, string value,
  618. char[] charBuffer, int charPos, int charLen, char[] coreNewLine,
  619. bool autoFlush, bool appendNewLine)
  620. {
  621. Debug.Assert(value != null);
  622. int count = value.Length;
  623. int index = 0;
  624. while (count > 0)
  625. {
  626. if (charPos == charLen)
  627. {
  628. await _this.FlushAsyncInternal(false, false, charBuffer, charPos).ConfigureAwait(false);
  629. Debug.Assert(_this._charPos == 0);
  630. charPos = 0;
  631. }
  632. int n = charLen - charPos;
  633. if (n > count)
  634. {
  635. n = count;
  636. }
  637. Debug.Assert(n > 0, "StreamWriter::Write(String) isn't making progress! This is most likely a race condition in user code.");
  638. value.CopyTo(index, charBuffer, charPos, n);
  639. charPos += n;
  640. index += n;
  641. count -= n;
  642. }
  643. if (appendNewLine)
  644. {
  645. for (int i = 0; i < coreNewLine.Length; i++) // Expect 2 iterations, no point calling BlockCopy
  646. {
  647. if (charPos == charLen)
  648. {
  649. await _this.FlushAsyncInternal(false, false, charBuffer, charPos).ConfigureAwait(false);
  650. Debug.Assert(_this._charPos == 0);
  651. charPos = 0;
  652. }
  653. charBuffer[charPos] = coreNewLine[i];
  654. charPos++;
  655. }
  656. }
  657. if (autoFlush)
  658. {
  659. await _this.FlushAsyncInternal(true, false, charBuffer, charPos).ConfigureAwait(false);
  660. Debug.Assert(_this._charPos == 0);
  661. charPos = 0;
  662. }
  663. _this._charPos = charPos;
  664. }
  665. public override Task WriteAsync(char[] buffer, int index, int count)
  666. {
  667. if (buffer == null)
  668. {
  669. throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
  670. }
  671. if (index < 0)
  672. {
  673. throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_NeedNonNegNum);
  674. }
  675. if (count < 0)
  676. {
  677. throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
  678. }
  679. if (buffer.Length - index < count)
  680. {
  681. throw new ArgumentException(SR.Argument_InvalidOffLen);
  682. }
  683. // If we have been inherited into a subclass, the following implementation could be incorrect
  684. // since it does not call through to Write() which a subclass might have overridden.
  685. // To be safe we will only use this implementation in cases where we know it is safe to do so,
  686. // and delegate to our base class (which will call into Write) when we are not sure.
  687. if (GetType() != typeof(StreamWriter))
  688. {
  689. return base.WriteAsync(buffer, index, count);
  690. }
  691. ThrowIfDisposed();
  692. CheckAsyncTaskInProgress();
  693. Task task = WriteAsyncInternal(this, new ReadOnlyMemory<char>(buffer, index, count), _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: false, cancellationToken: default);
  694. _asyncWriteTask = task;
  695. return task;
  696. }
  697. public override Task WriteAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken = default)
  698. {
  699. if (GetType() != typeof(StreamWriter))
  700. {
  701. // If a derived type may have overridden existing WriteASync(char[], ...) behavior, make sure we use it.
  702. return base.WriteAsync(buffer, cancellationToken);
  703. }
  704. ThrowIfDisposed();
  705. CheckAsyncTaskInProgress();
  706. if (cancellationToken.IsCancellationRequested)
  707. {
  708. return Task.FromCanceled(cancellationToken);
  709. }
  710. Task task = WriteAsyncInternal(this, buffer, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: false, cancellationToken: cancellationToken);
  711. _asyncWriteTask = task;
  712. return task;
  713. }
  714. // We pass in private instance fields of this MarshalByRefObject-derived type as local params
  715. // to ensure performant access inside the state machine that corresponds this async method.
  716. // Fields that are written to must be assigned at the end of the method *and* before instance invocations.
  717. private static async Task WriteAsyncInternal(StreamWriter _this, ReadOnlyMemory<char> source,
  718. char[] charBuffer, int charPos, int charLen, char[] coreNewLine,
  719. bool autoFlush, bool appendNewLine, CancellationToken cancellationToken)
  720. {
  721. int copied = 0;
  722. while (copied < source.Length)
  723. {
  724. if (charPos == charLen)
  725. {
  726. await _this.FlushAsyncInternal(false, false, charBuffer, charPos, cancellationToken).ConfigureAwait(false);
  727. Debug.Assert(_this._charPos == 0);
  728. charPos = 0;
  729. }
  730. int n = Math.Min(charLen - charPos, source.Length - copied);
  731. Debug.Assert(n > 0, "StreamWriter::Write(char[], int, int) isn't making progress! This is most likely a race condition in user code.");
  732. source.Span.Slice(copied, n).CopyTo(new Span<char>(charBuffer, charPos, n));
  733. charPos += n;
  734. copied += n;
  735. }
  736. if (appendNewLine)
  737. {
  738. for (int i = 0; i < coreNewLine.Length; i++) // Expect 2 iterations, no point calling BlockCopy
  739. {
  740. if (charPos == charLen)
  741. {
  742. await _this.FlushAsyncInternal(false, false, charBuffer, charPos, cancellationToken).ConfigureAwait(false);
  743. Debug.Assert(_this._charPos == 0);
  744. charPos = 0;
  745. }
  746. charBuffer[charPos] = coreNewLine[i];
  747. charPos++;
  748. }
  749. }
  750. if (autoFlush)
  751. {
  752. await _this.FlushAsyncInternal(true, false, charBuffer, charPos, cancellationToken).ConfigureAwait(false);
  753. Debug.Assert(_this._charPos == 0);
  754. charPos = 0;
  755. }
  756. _this._charPos = charPos;
  757. }
  758. public override Task WriteLineAsync()
  759. {
  760. // If we have been inherited into a subclass, the following implementation could be incorrect
  761. // since it does not call through to Write() which a subclass might have overridden.
  762. // To be safe we will only use this implementation in cases where we know it is safe to do so,
  763. // and delegate to our base class (which will call into Write) when we are not sure.
  764. if (GetType() != typeof(StreamWriter))
  765. {
  766. return base.WriteLineAsync();
  767. }
  768. ThrowIfDisposed();
  769. CheckAsyncTaskInProgress();
  770. Task task = WriteAsyncInternal(this, ReadOnlyMemory<char>.Empty, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: true, cancellationToken: default);
  771. _asyncWriteTask = task;
  772. return task;
  773. }
  774. public override Task WriteLineAsync(char value)
  775. {
  776. // If we have been inherited into a subclass, the following implementation could be incorrect
  777. // since it does not call through to Write() which a subclass might have overridden.
  778. // To be safe we will only use this implementation in cases where we know it is safe to do so,
  779. // and delegate to our base class (which will call into Write) when we are not sure.
  780. if (GetType() != typeof(StreamWriter))
  781. {
  782. return base.WriteLineAsync(value);
  783. }
  784. ThrowIfDisposed();
  785. CheckAsyncTaskInProgress();
  786. Task task = WriteAsyncInternal(this, value, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: true);
  787. _asyncWriteTask = task;
  788. return task;
  789. }
  790. public override Task WriteLineAsync(string? value)
  791. {
  792. if (value == null)
  793. {
  794. return WriteLineAsync();
  795. }
  796. // If we have been inherited into a subclass, the following implementation could be incorrect
  797. // since it does not call through to Write() which a subclass might have overridden.
  798. // To be safe we will only use this implementation in cases where we know it is safe to do so,
  799. // and delegate to our base class (which will call into Write) when we are not sure.
  800. if (GetType() != typeof(StreamWriter))
  801. {
  802. return base.WriteLineAsync(value);
  803. }
  804. ThrowIfDisposed();
  805. CheckAsyncTaskInProgress();
  806. Task task = WriteAsyncInternal(this, value, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: true);
  807. _asyncWriteTask = task;
  808. return task;
  809. }
  810. public override Task WriteLineAsync(char[] buffer, int index, int count)
  811. {
  812. if (buffer == null)
  813. {
  814. throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
  815. }
  816. if (index < 0)
  817. {
  818. throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_NeedNonNegNum);
  819. }
  820. if (count < 0)
  821. {
  822. throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
  823. }
  824. if (buffer.Length - index < count)
  825. {
  826. throw new ArgumentException(SR.Argument_InvalidOffLen);
  827. }
  828. // If we have been inherited into a subclass, the following implementation could be incorrect
  829. // since it does not call through to Write() which a subclass might have overridden.
  830. // To be safe we will only use this implementation in cases where we know it is safe to do so,
  831. // and delegate to our base class (which will call into Write) when we are not sure.
  832. if (GetType() != typeof(StreamWriter))
  833. {
  834. return base.WriteLineAsync(buffer, index, count);
  835. }
  836. ThrowIfDisposed();
  837. CheckAsyncTaskInProgress();
  838. Task task = WriteAsyncInternal(this, new ReadOnlyMemory<char>(buffer, index, count), _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: true, cancellationToken: default);
  839. _asyncWriteTask = task;
  840. return task;
  841. }
  842. public override Task WriteLineAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken = default)
  843. {
  844. if (GetType() != typeof(StreamWriter))
  845. {
  846. return base.WriteLineAsync(buffer, cancellationToken);
  847. }
  848. ThrowIfDisposed();
  849. CheckAsyncTaskInProgress();
  850. if (cancellationToken.IsCancellationRequested)
  851. {
  852. return Task.FromCanceled(cancellationToken);
  853. }
  854. Task task = WriteAsyncInternal(this, buffer, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: true, cancellationToken: cancellationToken);
  855. _asyncWriteTask = task;
  856. return task;
  857. }
  858. public override Task FlushAsync()
  859. {
  860. // If we have been inherited into a subclass, the following implementation could be incorrect
  861. // since it does not call through to Flush() which a subclass might have overridden. To be safe
  862. // we will only use this implementation in cases where we know it is safe to do so,
  863. // and delegate to our base class (which will call into Flush) when we are not sure.
  864. if (GetType() != typeof(StreamWriter))
  865. {
  866. return base.FlushAsync();
  867. }
  868. // flushEncoder should be true at the end of the file and if
  869. // the user explicitly calls Flush (though not if AutoFlush is true).
  870. // This is required to flush any dangling characters from our UTF-7
  871. // and UTF-8 encoders.
  872. ThrowIfDisposed();
  873. CheckAsyncTaskInProgress();
  874. Task task = FlushAsyncInternal(true, true, _charBuffer, _charPos);
  875. _asyncWriteTask = task;
  876. return task;
  877. }
  878. private Task FlushAsyncInternal(bool flushStream, bool flushEncoder,
  879. char[] sCharBuffer, int sCharPos, CancellationToken cancellationToken = default)
  880. {
  881. if (cancellationToken.IsCancellationRequested)
  882. {
  883. return Task.FromCanceled(cancellationToken);
  884. }
  885. // Perf boost for Flush on non-dirty writers.
  886. if (sCharPos == 0 && !flushStream && !flushEncoder)
  887. {
  888. return Task.CompletedTask;
  889. }
  890. Task flushTask = FlushAsyncInternal(this, flushStream, flushEncoder, sCharBuffer, sCharPos, _haveWrittenPreamble,
  891. _encoding, _encoder, _byteBuffer, _stream, cancellationToken);
  892. _charPos = 0;
  893. return flushTask;
  894. }
  895. // We pass in private instance fields of this MarshalByRefObject-derived type as local params
  896. // to ensure performant access inside the state machine that corresponds this async method.
  897. private static async Task FlushAsyncInternal(StreamWriter _this, bool flushStream, bool flushEncoder,
  898. char[] charBuffer, int charPos, bool haveWrittenPreamble,
  899. Encoding encoding, Encoder encoder, byte[] byteBuffer, Stream stream, CancellationToken cancellationToken)
  900. {
  901. if (!haveWrittenPreamble)
  902. {
  903. _this._haveWrittenPreamble = true;
  904. byte[] preamble = encoding.GetPreamble();
  905. if (preamble.Length > 0)
  906. {
  907. await stream.WriteAsync(new ReadOnlyMemory<byte>(preamble), cancellationToken).ConfigureAwait(false);
  908. }
  909. }
  910. int count = encoder.GetBytes(charBuffer, 0, charPos, byteBuffer, 0, flushEncoder);
  911. if (count > 0)
  912. {
  913. await stream.WriteAsync(new ReadOnlyMemory<byte>(byteBuffer, 0, count), cancellationToken).ConfigureAwait(false);
  914. }
  915. // By definition, calling Flush should flush the stream, but this is
  916. // only necessary if we passed in true for flushStream. The Web
  917. // Services guys have some perf tests where flushing needlessly hurts.
  918. if (flushStream)
  919. {
  920. await stream.FlushAsync(cancellationToken).ConfigureAwait(false);
  921. }
  922. }
  923. private void ThrowIfDisposed()
  924. {
  925. if (_disposed)
  926. {
  927. ThrowObjectDisposedException();
  928. }
  929. void ThrowObjectDisposedException() => throw new ObjectDisposedException(GetType().Name, SR.ObjectDisposed_WriterClosed);
  930. }
  931. } // class StreamWriter
  932. } // namespace