StreamWriter.cs 41 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078
  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 => _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 => _stream;
  275. public override Encoding Encoding => _encoding;
  276. public override void Write(char value)
  277. {
  278. CheckAsyncTaskInProgress();
  279. if (_charPos == _charLen)
  280. {
  281. Flush(false, false);
  282. }
  283. _charBuffer[_charPos] = value;
  284. _charPos++;
  285. if (_autoFlush)
  286. {
  287. Flush(true, false);
  288. }
  289. }
  290. [MethodImpl(MethodImplOptions.NoInlining)] // prevent WriteSpan from bloating call sites
  291. public override void Write(char[]? buffer)
  292. {
  293. WriteSpan(buffer, appendNewLine: false);
  294. }
  295. [MethodImpl(MethodImplOptions.NoInlining)] // prevent WriteSpan from bloating call sites
  296. public override void Write(char[] buffer, int index, int count)
  297. {
  298. if (buffer == null)
  299. {
  300. throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
  301. }
  302. if (index < 0)
  303. {
  304. throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_NeedNonNegNum);
  305. }
  306. if (count < 0)
  307. {
  308. throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
  309. }
  310. if (buffer.Length - index < count)
  311. {
  312. throw new ArgumentException(SR.Argument_InvalidOffLen);
  313. }
  314. WriteSpan(buffer.AsSpan(index, count), appendNewLine: false);
  315. }
  316. [MethodImpl(MethodImplOptions.NoInlining)] // prevent WriteSpan from bloating call sites
  317. public override void Write(ReadOnlySpan<char> buffer)
  318. {
  319. if (GetType() == typeof(StreamWriter))
  320. {
  321. WriteSpan(buffer, appendNewLine: false);
  322. }
  323. else
  324. {
  325. // If a derived class may have overridden existing Write behavior,
  326. // we need to make sure we use it.
  327. base.Write(buffer);
  328. }
  329. }
  330. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  331. private unsafe void WriteSpan(ReadOnlySpan<char> buffer, bool appendNewLine)
  332. {
  333. CheckAsyncTaskInProgress();
  334. if (buffer.Length <= 4 && // Threshold of 4 chosen based on perf experimentation
  335. buffer.Length <= _charLen - _charPos)
  336. {
  337. // For very short buffers and when we don't need to worry about running out of space
  338. // in the char buffer, just copy the chars individually.
  339. for (int i = 0; i < buffer.Length; i++)
  340. {
  341. _charBuffer[_charPos++] = buffer[i];
  342. }
  343. }
  344. else
  345. {
  346. // For larger buffers or when we may run out of room in the internal char buffer, copy in chunks.
  347. // Use unsafe code until https://github.com/dotnet/coreclr/issues/13827 is addressed, as spans are
  348. // resulting in significant overhead (even when the if branch above is taken rather than this
  349. // else) due to temporaries that need to be cleared. Given the use of unsafe code, we also
  350. // make local copies of instance state to protect against potential concurrent misuse.
  351. ThrowIfDisposed();
  352. char[] charBuffer = _charBuffer;
  353. fixed (char* bufferPtr = &MemoryMarshal.GetReference(buffer))
  354. fixed (char* dstPtr = &charBuffer[0])
  355. {
  356. char* srcPtr = bufferPtr;
  357. int count = buffer.Length;
  358. int dstPos = _charPos; // use a local copy of _charPos for safety
  359. while (count > 0)
  360. {
  361. if (dstPos == charBuffer.Length)
  362. {
  363. Flush(false, false);
  364. dstPos = 0;
  365. }
  366. int n = Math.Min(charBuffer.Length - dstPos, count);
  367. int bytesToCopy = n * sizeof(char);
  368. Buffer.MemoryCopy(srcPtr, dstPtr + dstPos, bytesToCopy, bytesToCopy);
  369. _charPos += n;
  370. dstPos += n;
  371. srcPtr += n;
  372. count -= n;
  373. }
  374. }
  375. }
  376. if (appendNewLine)
  377. {
  378. char[] coreNewLine = CoreNewLine;
  379. for (int i = 0; i < coreNewLine.Length; i++) // Generally 1 (\n) or 2 (\r\n) iterations
  380. {
  381. if (_charPos == _charLen)
  382. {
  383. Flush(false, false);
  384. }
  385. _charBuffer[_charPos] = coreNewLine[i];
  386. _charPos++;
  387. }
  388. }
  389. if (_autoFlush)
  390. {
  391. Flush(true, false);
  392. }
  393. }
  394. [MethodImpl(MethodImplOptions.NoInlining)] // prevent WriteSpan from bloating call sites
  395. public override void Write(string? value)
  396. {
  397. WriteSpan(value, appendNewLine: false);
  398. }
  399. [MethodImpl(MethodImplOptions.NoInlining)] // prevent WriteSpan from bloating call sites
  400. public override void WriteLine(string? value)
  401. {
  402. CheckAsyncTaskInProgress();
  403. WriteSpan(value, appendNewLine: true);
  404. }
  405. [MethodImpl(MethodImplOptions.NoInlining)] // prevent WriteSpan from bloating call sites
  406. public override void WriteLine(ReadOnlySpan<char> value)
  407. {
  408. if (GetType() == typeof(StreamWriter))
  409. {
  410. CheckAsyncTaskInProgress();
  411. WriteSpan(value, appendNewLine: true);
  412. }
  413. else
  414. {
  415. // If a derived class may have overridden existing WriteLine behavior,
  416. // we need to make sure we use it.
  417. base.WriteLine(value);
  418. }
  419. }
  420. private void WriteFormatHelper(string format, ParamsArray args, bool appendNewLine)
  421. {
  422. StringBuilder sb =
  423. StringBuilderCache.Acquire((format?.Length ?? 0) + args.Length * 8)
  424. .AppendFormatHelper(null, format!, args); // AppendFormatHelper will appropriately throw ArgumentNullException for a null format
  425. StringBuilder.ChunkEnumerator chunks = sb.GetChunks();
  426. bool more = chunks.MoveNext();
  427. while (more)
  428. {
  429. ReadOnlySpan<char> current = chunks.Current.Span;
  430. more = chunks.MoveNext();
  431. // If final chunk, include the newline if needed
  432. WriteSpan(current, appendNewLine: more ? false : appendNewLine);
  433. }
  434. StringBuilderCache.Release(sb);
  435. }
  436. public override void Write(string format, object? arg0)
  437. {
  438. if (GetType() == typeof(StreamWriter))
  439. {
  440. WriteFormatHelper(format, new ParamsArray(arg0), appendNewLine: false);
  441. }
  442. else
  443. {
  444. base.Write(format, arg0);
  445. }
  446. }
  447. public override void Write(string format, object? arg0, object? arg1)
  448. {
  449. if (GetType() == typeof(StreamWriter))
  450. {
  451. WriteFormatHelper(format, new ParamsArray(arg0, arg1), appendNewLine: false);
  452. }
  453. else
  454. {
  455. base.Write(format, arg0, arg1);
  456. }
  457. }
  458. public override void Write(string format, object? arg0, object? arg1, object? arg2)
  459. {
  460. if (GetType() == typeof(StreamWriter))
  461. {
  462. WriteFormatHelper(format, new ParamsArray(arg0, arg1, arg2), appendNewLine: false);
  463. }
  464. else
  465. {
  466. base.Write(format, arg0, arg1, arg2);
  467. }
  468. }
  469. public override void Write(string format, params object?[] arg)
  470. {
  471. if (GetType() == typeof(StreamWriter))
  472. {
  473. if (arg == null)
  474. {
  475. throw new ArgumentNullException((format == null) ? nameof(format) : nameof(arg)); // same as base logic
  476. }
  477. WriteFormatHelper(format, new ParamsArray(arg), appendNewLine: false);
  478. }
  479. else
  480. {
  481. base.Write(format, arg);
  482. }
  483. }
  484. public override void WriteLine(string format, object? arg0)
  485. {
  486. if (GetType() == typeof(StreamWriter))
  487. {
  488. WriteFormatHelper(format, new ParamsArray(arg0), appendNewLine: true);
  489. }
  490. else
  491. {
  492. base.WriteLine(format, arg0);
  493. }
  494. }
  495. public override void WriteLine(string format, object? arg0, object? arg1)
  496. {
  497. if (GetType() == typeof(StreamWriter))
  498. {
  499. WriteFormatHelper(format, new ParamsArray(arg0, arg1), appendNewLine: true);
  500. }
  501. else
  502. {
  503. base.WriteLine(format, arg0, arg1);
  504. }
  505. }
  506. public override void WriteLine(string format, object? arg0, object? arg1, object? arg2)
  507. {
  508. if (GetType() == typeof(StreamWriter))
  509. {
  510. WriteFormatHelper(format, new ParamsArray(arg0, arg1, arg2), appendNewLine: true);
  511. }
  512. else
  513. {
  514. base.WriteLine(format, arg0, arg1, arg2);
  515. }
  516. }
  517. public override void WriteLine(string format, params object?[] arg)
  518. {
  519. if (GetType() == typeof(StreamWriter))
  520. {
  521. if (arg == null)
  522. {
  523. throw new ArgumentNullException(nameof(arg));
  524. }
  525. WriteFormatHelper(format, new ParamsArray(arg), appendNewLine: true);
  526. }
  527. else
  528. {
  529. base.WriteLine(format, arg);
  530. }
  531. }
  532. public override Task WriteAsync(char value)
  533. {
  534. // If we have been inherited into a subclass, the following implementation could be incorrect
  535. // since it does not call through to Write() which a subclass might have overridden.
  536. // To be safe we will only use this implementation in cases where we know it is safe to do so,
  537. // and delegate to our base class (which will call into Write) when we are not sure.
  538. if (GetType() != typeof(StreamWriter))
  539. {
  540. return base.WriteAsync(value);
  541. }
  542. ThrowIfDisposed();
  543. CheckAsyncTaskInProgress();
  544. Task task = WriteAsyncInternal(this, value, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: false);
  545. _asyncWriteTask = task;
  546. return task;
  547. }
  548. // We pass in private instance fields of this MarshalByRefObject-derived type as local params
  549. // to ensure performant access inside the state machine that corresponds this async method.
  550. // Fields that are written to must be assigned at the end of the method *and* before instance invocations.
  551. private static async Task WriteAsyncInternal(StreamWriter _this, char value,
  552. char[] charBuffer, int charPos, int charLen, char[] coreNewLine,
  553. bool autoFlush, bool appendNewLine)
  554. {
  555. if (charPos == charLen)
  556. {
  557. await _this.FlushAsyncInternal(false, false, charBuffer, charPos).ConfigureAwait(false);
  558. Debug.Assert(_this._charPos == 0);
  559. charPos = 0;
  560. }
  561. charBuffer[charPos] = value;
  562. charPos++;
  563. if (appendNewLine)
  564. {
  565. for (int i = 0; i < coreNewLine.Length; i++) // Expect 2 iterations, no point calling BlockCopy
  566. {
  567. if (charPos == charLen)
  568. {
  569. await _this.FlushAsyncInternal(false, false, charBuffer, charPos).ConfigureAwait(false);
  570. Debug.Assert(_this._charPos == 0);
  571. charPos = 0;
  572. }
  573. charBuffer[charPos] = coreNewLine[i];
  574. charPos++;
  575. }
  576. }
  577. if (autoFlush)
  578. {
  579. await _this.FlushAsyncInternal(true, false, charBuffer, charPos).ConfigureAwait(false);
  580. Debug.Assert(_this._charPos == 0);
  581. charPos = 0;
  582. }
  583. _this._charPos = charPos;
  584. }
  585. public override Task WriteAsync(string? value)
  586. {
  587. // If we have been inherited into a subclass, the following implementation could be incorrect
  588. // since it does not call through to Write() which a subclass might have overridden.
  589. // To be safe we will only use this implementation in cases where we know it is safe to do so,
  590. // and delegate to our base class (which will call into Write) when we are not sure.
  591. if (GetType() != typeof(StreamWriter))
  592. {
  593. return base.WriteAsync(value);
  594. }
  595. if (value != null)
  596. {
  597. ThrowIfDisposed();
  598. CheckAsyncTaskInProgress();
  599. Task task = WriteAsyncInternal(this, value, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: false);
  600. _asyncWriteTask = task;
  601. return task;
  602. }
  603. else
  604. {
  605. return Task.CompletedTask;
  606. }
  607. }
  608. // We pass in private instance fields of this MarshalByRefObject-derived type as local params
  609. // to ensure performant access inside the state machine that corresponds this async method.
  610. // Fields that are written to must be assigned at the end of the method *and* before instance invocations.
  611. private static async Task WriteAsyncInternal(StreamWriter _this, string value,
  612. char[] charBuffer, int charPos, int charLen, char[] coreNewLine,
  613. bool autoFlush, bool appendNewLine)
  614. {
  615. Debug.Assert(value != null);
  616. int count = value.Length;
  617. int index = 0;
  618. while (count > 0)
  619. {
  620. if (charPos == charLen)
  621. {
  622. await _this.FlushAsyncInternal(false, false, charBuffer, charPos).ConfigureAwait(false);
  623. Debug.Assert(_this._charPos == 0);
  624. charPos = 0;
  625. }
  626. int n = charLen - charPos;
  627. if (n > count)
  628. {
  629. n = count;
  630. }
  631. Debug.Assert(n > 0, "StreamWriter::Write(String) isn't making progress! This is most likely a race condition in user code.");
  632. value.CopyTo(index, charBuffer, charPos, n);
  633. charPos += n;
  634. index += n;
  635. count -= n;
  636. }
  637. if (appendNewLine)
  638. {
  639. for (int i = 0; i < coreNewLine.Length; i++) // Expect 2 iterations, no point calling BlockCopy
  640. {
  641. if (charPos == charLen)
  642. {
  643. await _this.FlushAsyncInternal(false, false, charBuffer, charPos).ConfigureAwait(false);
  644. Debug.Assert(_this._charPos == 0);
  645. charPos = 0;
  646. }
  647. charBuffer[charPos] = coreNewLine[i];
  648. charPos++;
  649. }
  650. }
  651. if (autoFlush)
  652. {
  653. await _this.FlushAsyncInternal(true, false, charBuffer, charPos).ConfigureAwait(false);
  654. Debug.Assert(_this._charPos == 0);
  655. charPos = 0;
  656. }
  657. _this._charPos = charPos;
  658. }
  659. public override Task WriteAsync(char[] buffer, int index, int count)
  660. {
  661. if (buffer == null)
  662. {
  663. throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
  664. }
  665. if (index < 0)
  666. {
  667. throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_NeedNonNegNum);
  668. }
  669. if (count < 0)
  670. {
  671. throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
  672. }
  673. if (buffer.Length - index < count)
  674. {
  675. throw new ArgumentException(SR.Argument_InvalidOffLen);
  676. }
  677. // If we have been inherited into a subclass, the following implementation could be incorrect
  678. // since it does not call through to Write() which a subclass might have overridden.
  679. // To be safe we will only use this implementation in cases where we know it is safe to do so,
  680. // and delegate to our base class (which will call into Write) when we are not sure.
  681. if (GetType() != typeof(StreamWriter))
  682. {
  683. return base.WriteAsync(buffer, index, count);
  684. }
  685. ThrowIfDisposed();
  686. CheckAsyncTaskInProgress();
  687. Task task = WriteAsyncInternal(this, new ReadOnlyMemory<char>(buffer, index, count), _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: false, cancellationToken: default);
  688. _asyncWriteTask = task;
  689. return task;
  690. }
  691. public override Task WriteAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken = default)
  692. {
  693. if (GetType() != typeof(StreamWriter))
  694. {
  695. // If a derived type may have overridden existing WriteASync(char[], ...) behavior, make sure we use it.
  696. return base.WriteAsync(buffer, cancellationToken);
  697. }
  698. ThrowIfDisposed();
  699. CheckAsyncTaskInProgress();
  700. if (cancellationToken.IsCancellationRequested)
  701. {
  702. return Task.FromCanceled(cancellationToken);
  703. }
  704. Task task = WriteAsyncInternal(this, buffer, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: false, cancellationToken: cancellationToken);
  705. _asyncWriteTask = task;
  706. return task;
  707. }
  708. // We pass in private instance fields of this MarshalByRefObject-derived type as local params
  709. // to ensure performant access inside the state machine that corresponds this async method.
  710. // Fields that are written to must be assigned at the end of the method *and* before instance invocations.
  711. private static async Task WriteAsyncInternal(StreamWriter _this, ReadOnlyMemory<char> source,
  712. char[] charBuffer, int charPos, int charLen, char[] coreNewLine,
  713. bool autoFlush, bool appendNewLine, CancellationToken cancellationToken)
  714. {
  715. int copied = 0;
  716. while (copied < source.Length)
  717. {
  718. if (charPos == charLen)
  719. {
  720. await _this.FlushAsyncInternal(false, false, charBuffer, charPos, cancellationToken).ConfigureAwait(false);
  721. Debug.Assert(_this._charPos == 0);
  722. charPos = 0;
  723. }
  724. int n = Math.Min(charLen - charPos, source.Length - copied);
  725. Debug.Assert(n > 0, "StreamWriter::Write(char[], int, int) isn't making progress! This is most likely a race condition in user code.");
  726. source.Span.Slice(copied, n).CopyTo(new Span<char>(charBuffer, charPos, n));
  727. charPos += n;
  728. copied += n;
  729. }
  730. if (appendNewLine)
  731. {
  732. for (int i = 0; i < coreNewLine.Length; i++) // Expect 2 iterations, no point calling BlockCopy
  733. {
  734. if (charPos == charLen)
  735. {
  736. await _this.FlushAsyncInternal(false, false, charBuffer, charPos, cancellationToken).ConfigureAwait(false);
  737. Debug.Assert(_this._charPos == 0);
  738. charPos = 0;
  739. }
  740. charBuffer[charPos] = coreNewLine[i];
  741. charPos++;
  742. }
  743. }
  744. if (autoFlush)
  745. {
  746. await _this.FlushAsyncInternal(true, false, charBuffer, charPos, cancellationToken).ConfigureAwait(false);
  747. Debug.Assert(_this._charPos == 0);
  748. charPos = 0;
  749. }
  750. _this._charPos = charPos;
  751. }
  752. public override Task WriteLineAsync()
  753. {
  754. // If we have been inherited into a subclass, the following implementation could be incorrect
  755. // since it does not call through to Write() which a subclass might have overridden.
  756. // To be safe we will only use this implementation in cases where we know it is safe to do so,
  757. // and delegate to our base class (which will call into Write) when we are not sure.
  758. if (GetType() != typeof(StreamWriter))
  759. {
  760. return base.WriteLineAsync();
  761. }
  762. ThrowIfDisposed();
  763. CheckAsyncTaskInProgress();
  764. Task task = WriteAsyncInternal(this, ReadOnlyMemory<char>.Empty, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: true, cancellationToken: default);
  765. _asyncWriteTask = task;
  766. return task;
  767. }
  768. public override Task WriteLineAsync(char value)
  769. {
  770. // If we have been inherited into a subclass, the following implementation could be incorrect
  771. // since it does not call through to Write() which a subclass might have overridden.
  772. // To be safe we will only use this implementation in cases where we know it is safe to do so,
  773. // and delegate to our base class (which will call into Write) when we are not sure.
  774. if (GetType() != typeof(StreamWriter))
  775. {
  776. return base.WriteLineAsync(value);
  777. }
  778. ThrowIfDisposed();
  779. CheckAsyncTaskInProgress();
  780. Task task = WriteAsyncInternal(this, value, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: true);
  781. _asyncWriteTask = task;
  782. return task;
  783. }
  784. public override Task WriteLineAsync(string? value)
  785. {
  786. if (value == null)
  787. {
  788. return WriteLineAsync();
  789. }
  790. // If we have been inherited into a subclass, the following implementation could be incorrect
  791. // since it does not call through to Write() which a subclass might have overridden.
  792. // To be safe we will only use this implementation in cases where we know it is safe to do so,
  793. // and delegate to our base class (which will call into Write) when we are not sure.
  794. if (GetType() != typeof(StreamWriter))
  795. {
  796. return base.WriteLineAsync(value);
  797. }
  798. ThrowIfDisposed();
  799. CheckAsyncTaskInProgress();
  800. Task task = WriteAsyncInternal(this, value, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: true);
  801. _asyncWriteTask = task;
  802. return task;
  803. }
  804. public override Task WriteLineAsync(char[] buffer, int index, int count)
  805. {
  806. if (buffer == null)
  807. {
  808. throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
  809. }
  810. if (index < 0)
  811. {
  812. throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_NeedNonNegNum);
  813. }
  814. if (count < 0)
  815. {
  816. throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
  817. }
  818. if (buffer.Length - index < count)
  819. {
  820. throw new ArgumentException(SR.Argument_InvalidOffLen);
  821. }
  822. // If we have been inherited into a subclass, the following implementation could be incorrect
  823. // since it does not call through to Write() which a subclass might have overridden.
  824. // To be safe we will only use this implementation in cases where we know it is safe to do so,
  825. // and delegate to our base class (which will call into Write) when we are not sure.
  826. if (GetType() != typeof(StreamWriter))
  827. {
  828. return base.WriteLineAsync(buffer, index, count);
  829. }
  830. ThrowIfDisposed();
  831. CheckAsyncTaskInProgress();
  832. Task task = WriteAsyncInternal(this, new ReadOnlyMemory<char>(buffer, index, count), _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: true, cancellationToken: default);
  833. _asyncWriteTask = task;
  834. return task;
  835. }
  836. public override Task WriteLineAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken = default)
  837. {
  838. if (GetType() != typeof(StreamWriter))
  839. {
  840. return base.WriteLineAsync(buffer, cancellationToken);
  841. }
  842. ThrowIfDisposed();
  843. CheckAsyncTaskInProgress();
  844. if (cancellationToken.IsCancellationRequested)
  845. {
  846. return Task.FromCanceled(cancellationToken);
  847. }
  848. Task task = WriteAsyncInternal(this, buffer, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: true, cancellationToken: cancellationToken);
  849. _asyncWriteTask = task;
  850. return task;
  851. }
  852. public override Task FlushAsync()
  853. {
  854. // If we have been inherited into a subclass, the following implementation could be incorrect
  855. // since it does not call through to Flush() which a subclass might have overridden. To be safe
  856. // we will only use this implementation in cases where we know it is safe to do so,
  857. // and delegate to our base class (which will call into Flush) when we are not sure.
  858. if (GetType() != typeof(StreamWriter))
  859. {
  860. return base.FlushAsync();
  861. }
  862. // flushEncoder should be true at the end of the file and if
  863. // the user explicitly calls Flush (though not if AutoFlush is true).
  864. // This is required to flush any dangling characters from our UTF-7
  865. // and UTF-8 encoders.
  866. ThrowIfDisposed();
  867. CheckAsyncTaskInProgress();
  868. Task task = FlushAsyncInternal(true, true, _charBuffer, _charPos);
  869. _asyncWriteTask = task;
  870. return task;
  871. }
  872. private Task FlushAsyncInternal(bool flushStream, bool flushEncoder,
  873. char[] sCharBuffer, int sCharPos, CancellationToken cancellationToken = default)
  874. {
  875. if (cancellationToken.IsCancellationRequested)
  876. {
  877. return Task.FromCanceled(cancellationToken);
  878. }
  879. // Perf boost for Flush on non-dirty writers.
  880. if (sCharPos == 0 && !flushStream && !flushEncoder)
  881. {
  882. return Task.CompletedTask;
  883. }
  884. Task flushTask = FlushAsyncInternal(this, flushStream, flushEncoder, sCharBuffer, sCharPos, _haveWrittenPreamble,
  885. _encoding, _encoder, _byteBuffer, _stream, cancellationToken);
  886. _charPos = 0;
  887. return flushTask;
  888. }
  889. // We pass in private instance fields of this MarshalByRefObject-derived type as local params
  890. // to ensure performant access inside the state machine that corresponds this async method.
  891. private static async Task FlushAsyncInternal(StreamWriter _this, bool flushStream, bool flushEncoder,
  892. char[] charBuffer, int charPos, bool haveWrittenPreamble,
  893. Encoding encoding, Encoder encoder, byte[] byteBuffer, Stream stream, CancellationToken cancellationToken)
  894. {
  895. if (!haveWrittenPreamble)
  896. {
  897. _this._haveWrittenPreamble = true;
  898. byte[] preamble = encoding.GetPreamble();
  899. if (preamble.Length > 0)
  900. {
  901. await stream.WriteAsync(new ReadOnlyMemory<byte>(preamble), cancellationToken).ConfigureAwait(false);
  902. }
  903. }
  904. int count = encoder.GetBytes(charBuffer, 0, charPos, byteBuffer, 0, flushEncoder);
  905. if (count > 0)
  906. {
  907. await stream.WriteAsync(new ReadOnlyMemory<byte>(byteBuffer, 0, count), cancellationToken).ConfigureAwait(false);
  908. }
  909. // By definition, calling Flush should flush the stream, but this is
  910. // only necessary if we passed in true for flushStream. The Web
  911. // Services guys have some perf tests where flushing needlessly hurts.
  912. if (flushStream)
  913. {
  914. await stream.FlushAsync(cancellationToken).ConfigureAwait(false);
  915. }
  916. }
  917. private void ThrowIfDisposed()
  918. {
  919. if (_disposed)
  920. {
  921. ThrowObjectDisposedException();
  922. }
  923. void ThrowObjectDisposedException() => throw new ObjectDisposedException(GetType().Name, SR.ObjectDisposed_WriterClosed);
  924. }
  925. } // class StreamWriter
  926. } // namespace