/src/CsvHelper/CsvWriter.cs

https://github.com/JoshClose/CsvHelper · C# · 855 lines · 643 code · 118 blank · 94 comment · 112 complexity · e44cff9f29d67993e04e111ee8067c97 MD5 · raw file

  1. // Copyright 2009-2021 Josh Close
  2. // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0.
  3. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0.
  4. // https://github.com/JoshClose/CsvHelper
  5. using System;
  6. using System.Collections;
  7. using System.Collections.Generic;
  8. using System.IO;
  9. using System.Reflection;
  10. using CsvHelper.Configuration;
  11. using CsvHelper.TypeConversion;
  12. using System.Linq;
  13. using System.Linq.Expressions;
  14. using System.Dynamic;
  15. using System.Threading.Tasks;
  16. using CsvHelper.Expressions;
  17. using System.Globalization;
  18. using System.Runtime.CompilerServices;
  19. using System.Text;
  20. using System.Buffers;
  21. using System.Threading;
  22. #pragma warning disable 649
  23. #pragma warning disable 169
  24. namespace CsvHelper
  25. {
  26. /// <summary>
  27. /// Used to write CSV files.
  28. /// </summary>
  29. public class CsvWriter : IWriter
  30. {
  31. private readonly TextWriter writer;
  32. private readonly CsvContext context;
  33. private readonly Lazy<RecordManager> recordManager;
  34. private readonly TypeConverterCache typeConverterCache;
  35. private readonly TrimOptions trimOptions;
  36. private readonly ShouldQuote shouldQuote;
  37. private readonly MemberMapData reusableMemberMapData = new MemberMapData(null);
  38. private readonly Dictionary<Type, TypeConverterOptions> typeConverterOptionsCache = new Dictionary<Type, TypeConverterOptions>();
  39. private readonly string quoteString;
  40. private readonly char quote;
  41. private readonly CultureInfo cultureInfo;
  42. private readonly char comment;
  43. private readonly bool hasHeaderRecord;
  44. private readonly bool includePrivateMembers;
  45. private readonly IComparer<string> dynamicPropertySort;
  46. private readonly string delimiter;
  47. private readonly bool leaveOpen;
  48. private readonly string newLine;
  49. private readonly char[] injectionCharacters;
  50. private readonly char injectionEscapeCharacter;
  51. private readonly bool sanitizeForInjection;
  52. private readonly CsvMode mode;
  53. private readonly string escapeQuoteString;
  54. private readonly string escapeDelimiterString;
  55. private readonly string escapeNewlineString;
  56. private bool disposed;
  57. private bool hasHeaderBeenWritten;
  58. private int row = 1;
  59. private int index;
  60. private char[] buffer;
  61. private int bufferSize;
  62. private int bufferPosition;
  63. private Type fieldType;
  64. /// <inheritdoc/>
  65. public virtual string[] HeaderRecord { get; private set; }
  66. /// <inheritdoc/>
  67. public virtual int Row => row;
  68. /// <inheritdoc/>
  69. public virtual int Index => index;
  70. /// <inheritdoc/>
  71. public virtual CsvContext Context => context;
  72. /// <inheritdoc/>
  73. public virtual IWriterConfiguration Configuration { get; private set; }
  74. /// <summary>
  75. /// Initializes a new instance of the <see cref="CsvWriter"/> class.
  76. /// </summary>
  77. /// <param name="writer">The writer.</param>
  78. /// <param name="culture">The culture.</param>
  79. /// <param name="leaveOpen"><c>true</c> to leave the <see cref="TextWriter"/> open after the <see cref="CsvWriter"/> object is disposed, otherwise <c>false</c>.</param>
  80. public CsvWriter(TextWriter writer, CultureInfo culture, bool leaveOpen = false) : this(writer, new CsvConfiguration(culture) { LeaveOpen = leaveOpen }) { }
  81. /// <summary>
  82. /// Initializes a new instance of the <see cref="CsvWriter"/> class.
  83. /// </summary>
  84. /// <param name="writer">The writer.</param>
  85. /// <param name="configuration">The configuration.</param>
  86. public CsvWriter(TextWriter writer, CsvConfiguration configuration)
  87. {
  88. configuration.Validate();
  89. this.writer = writer;
  90. Configuration = configuration;
  91. context = new CsvContext(this);
  92. typeConverterCache = context.TypeConverterCache;
  93. recordManager = new Lazy<RecordManager>(() => ObjectResolver.Current.Resolve<RecordManager>(this));
  94. comment = configuration.Comment;
  95. bufferSize = configuration.BufferSize;
  96. delimiter = configuration.Delimiter;
  97. cultureInfo = configuration.CultureInfo;
  98. dynamicPropertySort = configuration.DynamicPropertySort;
  99. escapeDelimiterString = new string(configuration.Delimiter.SelectMany(c => new[] { configuration.Escape, c }).ToArray());
  100. escapeNewlineString = new string(configuration.NewLine.SelectMany(c => new[] { configuration.Escape, c }).ToArray());
  101. escapeQuoteString = new string(new[] { configuration.Escape, configuration.Quote });
  102. hasHeaderRecord = configuration.HasHeaderRecord;
  103. includePrivateMembers = configuration.IncludePrivateMembers;
  104. injectionCharacters = configuration.InjectionCharacters;
  105. injectionEscapeCharacter = configuration.InjectionEscapeCharacter;
  106. leaveOpen = configuration.LeaveOpen;
  107. mode = configuration.Mode;
  108. newLine = configuration.NewLine;
  109. quote = configuration.Quote;
  110. quoteString = configuration.Quote.ToString();
  111. sanitizeForInjection = configuration.SanitizeForInjection;
  112. shouldQuote = configuration.ShouldQuote;
  113. trimOptions = configuration.TrimOptions;
  114. buffer = new char[bufferSize];
  115. }
  116. /// <inheritdoc/>
  117. public virtual void WriteConvertedField(string field, Type fieldType)
  118. {
  119. this.fieldType = fieldType;
  120. if (field == null)
  121. {
  122. return;
  123. }
  124. WriteField(field);
  125. }
  126. /// <inheritdoc/>
  127. public virtual void WriteField(string field)
  128. {
  129. if (field != null && (trimOptions & TrimOptions.Trim) == TrimOptions.Trim)
  130. {
  131. field = field.Trim();
  132. }
  133. fieldType ??= typeof(string);
  134. var args = new ShouldQuoteArgs(field, fieldType, this);
  135. var shouldQuoteResult = shouldQuote(args);
  136. WriteField(field, shouldQuoteResult);
  137. }
  138. /// <inheritdoc/>
  139. public virtual void WriteField(string field, bool shouldQuote)
  140. {
  141. if (mode == CsvMode.RFC4180)
  142. {
  143. // All quotes must be escaped.
  144. if (shouldQuote)
  145. {
  146. field = field?.Replace(quoteString, escapeQuoteString);
  147. field = quote + field + quote;
  148. }
  149. }
  150. else if (mode == CsvMode.Escape)
  151. {
  152. field = field?
  153. .Replace(quoteString, escapeQuoteString)
  154. .Replace(delimiter, escapeDelimiterString)
  155. .Replace(newLine, escapeNewlineString);
  156. }
  157. if (sanitizeForInjection)
  158. {
  159. field = SanitizeForInjection(field);
  160. }
  161. if (index > 0)
  162. {
  163. WriteToBuffer(delimiter);
  164. }
  165. WriteToBuffer(field);
  166. index++;
  167. fieldType = null;
  168. }
  169. /// <inheritdoc/>
  170. public virtual void WriteField<T>(T field)
  171. {
  172. var type = field == null ? typeof(string) : field.GetType();
  173. var converter = typeConverterCache.GetConverter(type);
  174. WriteField(field, converter);
  175. }
  176. /// <inheritdoc/>
  177. public virtual void WriteField<T>(T field, ITypeConverter converter)
  178. {
  179. var type = field == null ? typeof(string) : field.GetType();
  180. reusableMemberMapData.TypeConverter = converter;
  181. if (!typeConverterOptionsCache.TryGetValue(type, out TypeConverterOptions typeConverterOptions))
  182. {
  183. typeConverterOptions = TypeConverterOptions.Merge(new TypeConverterOptions { CultureInfo = cultureInfo }, context.TypeConverterOptionsCache.GetOptions(type));
  184. typeConverterOptionsCache.Add(type, typeConverterOptions);
  185. }
  186. reusableMemberMapData.TypeConverterOptions = typeConverterOptions;
  187. var fieldString = converter.ConvertToString(field, this, reusableMemberMapData);
  188. WriteConvertedField(fieldString, type);
  189. }
  190. /// <inheritdoc/>
  191. public virtual void WriteField<T, TConverter>(T field)
  192. {
  193. var converter = typeConverterCache.GetConverter<TConverter>();
  194. WriteField(field, converter);
  195. }
  196. /// <inheritdoc/>
  197. public virtual void WriteComment(string text)
  198. {
  199. WriteField(comment + text, false);
  200. }
  201. /// <inheritdoc/>
  202. public virtual void WriteHeader<T>()
  203. {
  204. WriteHeader(typeof(T));
  205. }
  206. /// <inheritdoc/>
  207. public virtual void WriteHeader(Type type)
  208. {
  209. if (type == null)
  210. {
  211. throw new ArgumentNullException(nameof(type));
  212. }
  213. if (type == typeof(object))
  214. {
  215. return;
  216. }
  217. if (context.Maps[type] == null)
  218. {
  219. context.Maps.Add(context.AutoMap(type));
  220. }
  221. var members = new MemberMapCollection();
  222. members.AddMembers(context.Maps[type]);
  223. var headerRecord = new List<string>();
  224. foreach (var member in members)
  225. {
  226. if (CanWrite(member))
  227. {
  228. if (member.Data.IndexEnd >= member.Data.Index)
  229. {
  230. var count = member.Data.IndexEnd - member.Data.Index + 1;
  231. for (var i = 1; i <= count; i++)
  232. {
  233. var header = member.Data.Names.FirstOrDefault() + i;
  234. WriteField(header);
  235. headerRecord.Add(header);
  236. }
  237. }
  238. else
  239. {
  240. var header = member.Data.Names.FirstOrDefault();
  241. WriteField(header);
  242. headerRecord.Add(header);
  243. }
  244. }
  245. }
  246. HeaderRecord = headerRecord.ToArray();
  247. hasHeaderBeenWritten = true;
  248. }
  249. /// <inheritdoc/>
  250. public virtual void WriteDynamicHeader(IDynamicMetaObjectProvider record)
  251. {
  252. if (record == null)
  253. {
  254. throw new ArgumentNullException(nameof(record));
  255. }
  256. var metaObject = record.GetMetaObject(Expression.Constant(record));
  257. var names = metaObject.GetDynamicMemberNames().ToList();
  258. if (dynamicPropertySort != null)
  259. {
  260. names = names.OrderBy(name => name, dynamicPropertySort).ToList();
  261. }
  262. HeaderRecord = names.ToArray();
  263. foreach (var name in names)
  264. {
  265. WriteField(name);
  266. }
  267. hasHeaderBeenWritten = true;
  268. }
  269. /// <inheritdoc/>
  270. public virtual void WriteRecord<T>(T record)
  271. {
  272. if (record is IDynamicMetaObjectProvider dynamicRecord)
  273. {
  274. if (hasHeaderRecord && !hasHeaderBeenWritten)
  275. {
  276. WriteDynamicHeader(dynamicRecord);
  277. NextRecord();
  278. }
  279. }
  280. try
  281. {
  282. recordManager.Value.Write(record);
  283. hasHeaderBeenWritten = true;
  284. }
  285. catch (Exception ex)
  286. {
  287. throw ex as CsvHelperException ?? new WriterException(context, "An unexpected error occurred.", ex);
  288. }
  289. }
  290. /// <inheritdoc/>
  291. public virtual void WriteRecords(IEnumerable records)
  292. {
  293. // Changes in this method require changes in method WriteRecords<T>(IEnumerable<T> records) also.
  294. try
  295. {
  296. foreach (var record in records)
  297. {
  298. var recordType = record.GetType();
  299. if (record is IDynamicMetaObjectProvider dynamicObject)
  300. {
  301. if (hasHeaderRecord && !hasHeaderBeenWritten)
  302. {
  303. WriteDynamicHeader(dynamicObject);
  304. NextRecord();
  305. }
  306. }
  307. else
  308. {
  309. // If records is a List<dynamic>, the header hasn't been written yet.
  310. // Write the header based on the record type.
  311. var isPrimitive = recordType.GetTypeInfo().IsPrimitive;
  312. if (hasHeaderRecord && !hasHeaderBeenWritten && !isPrimitive)
  313. {
  314. WriteHeader(recordType);
  315. NextRecord();
  316. }
  317. }
  318. try
  319. {
  320. recordManager.Value.Write(record);
  321. }
  322. catch (TargetInvocationException ex)
  323. {
  324. throw ex.InnerException;
  325. }
  326. NextRecord();
  327. }
  328. }
  329. catch (Exception ex)
  330. {
  331. throw ex as CsvHelperException ?? new WriterException(context, "An unexpected error occurred.", ex);
  332. }
  333. }
  334. /// <inheritdoc/>
  335. public virtual void WriteRecords<T>(IEnumerable<T> records)
  336. {
  337. // Changes in this method require changes in method WriteRecords(IEnumerable records) also.
  338. try
  339. {
  340. // Write the header. If records is a List<dynamic>, the header won't be written.
  341. // This is because typeof( T ) = Object.
  342. var recordType = typeof(T);
  343. var isPrimitive = recordType.GetTypeInfo().IsPrimitive;
  344. if (hasHeaderRecord && !hasHeaderBeenWritten && !isPrimitive && recordType != typeof(object))
  345. {
  346. WriteHeader(recordType);
  347. if (hasHeaderBeenWritten)
  348. {
  349. NextRecord();
  350. }
  351. }
  352. var getRecordType = recordType == typeof(object);
  353. foreach (var record in records)
  354. {
  355. if (getRecordType)
  356. {
  357. recordType = record.GetType();
  358. }
  359. if (record is IDynamicMetaObjectProvider dynamicObject)
  360. {
  361. if (hasHeaderRecord && !hasHeaderBeenWritten)
  362. {
  363. WriteDynamicHeader(dynamicObject);
  364. NextRecord();
  365. }
  366. }
  367. else
  368. {
  369. // If records is a List<dynamic>, the header hasn't been written yet.
  370. // Write the header based on the record type.
  371. isPrimitive = recordType.GetTypeInfo().IsPrimitive;
  372. if (hasHeaderRecord && !hasHeaderBeenWritten && !isPrimitive)
  373. {
  374. WriteHeader(recordType);
  375. NextRecord();
  376. }
  377. }
  378. try
  379. {
  380. recordManager.Value.Write(record);
  381. }
  382. catch (TargetInvocationException ex)
  383. {
  384. throw ex.InnerException;
  385. }
  386. NextRecord();
  387. }
  388. }
  389. catch (Exception ex)
  390. {
  391. throw ex as CsvHelperException ?? new WriterException(context, "An unexpected error occurred.", ex);
  392. }
  393. }
  394. /// <inheritdoc/>
  395. public virtual async Task WriteRecordsAsync(IEnumerable records, CancellationToken cancellationToken = default)
  396. {
  397. // These methods should all be the same;
  398. // - WriteRecordsAsync(IEnumerable records)
  399. // - WriteRecordsAsync<T>(IEnumerable<T> records)
  400. // - WriteRecordsAsync<T>(IAsyncEnumerable<T> records)
  401. try
  402. {
  403. foreach (var record in records)
  404. {
  405. cancellationToken.ThrowIfCancellationRequested();
  406. var recordType = record.GetType();
  407. if (record is IDynamicMetaObjectProvider dynamicObject)
  408. {
  409. if (hasHeaderRecord && !hasHeaderBeenWritten)
  410. {
  411. WriteDynamicHeader(dynamicObject);
  412. await NextRecordAsync().ConfigureAwait(false);
  413. }
  414. }
  415. else
  416. {
  417. // If records is a List<dynamic>, the header hasn't been written yet.
  418. // Write the header based on the record type.
  419. var isPrimitive = recordType.GetTypeInfo().IsPrimitive;
  420. if (hasHeaderRecord && !hasHeaderBeenWritten && !isPrimitive)
  421. {
  422. WriteHeader(recordType);
  423. await NextRecordAsync().ConfigureAwait(false);
  424. }
  425. }
  426. try
  427. {
  428. recordManager.Value.Write(record);
  429. }
  430. catch (TargetInvocationException ex)
  431. {
  432. throw ex.InnerException;
  433. }
  434. await NextRecordAsync().ConfigureAwait(false);
  435. }
  436. }
  437. catch (Exception ex)
  438. {
  439. throw ex as CsvHelperException ?? new WriterException(context, "An unexpected error occurred.", ex);
  440. }
  441. }
  442. /// <inheritdoc/>
  443. public virtual async Task WriteRecordsAsync<T>(IEnumerable<T> records, CancellationToken cancellationToken = default)
  444. {
  445. // These methods should all be the same;
  446. // - WriteRecordsAsync(IEnumerable records)
  447. // - WriteRecordsAsync<T>(IEnumerable<T> records)
  448. // - WriteRecordsAsync<T>(IAsyncEnumerable<T> records)
  449. try
  450. {
  451. // Write the header. If records is a List<dynamic>, the header won't be written.
  452. // This is because typeof( T ) = Object.
  453. var recordType = typeof(T);
  454. var isPrimitive = recordType.GetTypeInfo().IsPrimitive;
  455. if (hasHeaderRecord && !hasHeaderBeenWritten && !isPrimitive && recordType != typeof(object))
  456. {
  457. WriteHeader(recordType);
  458. if (hasHeaderBeenWritten)
  459. {
  460. await NextRecordAsync().ConfigureAwait(false);
  461. }
  462. }
  463. var getRecordType = recordType == typeof(object);
  464. foreach (var record in records)
  465. {
  466. cancellationToken.ThrowIfCancellationRequested();
  467. if (getRecordType)
  468. {
  469. recordType = record.GetType();
  470. }
  471. if (record is IDynamicMetaObjectProvider dynamicObject)
  472. {
  473. if (hasHeaderRecord && !hasHeaderBeenWritten)
  474. {
  475. WriteDynamicHeader(dynamicObject);
  476. await NextRecordAsync().ConfigureAwait(false);
  477. }
  478. }
  479. else
  480. {
  481. // If records is a List<dynamic>, the header hasn't been written yet.
  482. // Write the header based on the record type.
  483. isPrimitive = recordType.GetTypeInfo().IsPrimitive;
  484. if (hasHeaderRecord && !hasHeaderBeenWritten && !isPrimitive)
  485. {
  486. WriteHeader(recordType);
  487. await NextRecordAsync().ConfigureAwait(false);
  488. }
  489. }
  490. try
  491. {
  492. recordManager.Value.Write(record);
  493. }
  494. catch (TargetInvocationException ex)
  495. {
  496. throw ex.InnerException;
  497. }
  498. await NextRecordAsync().ConfigureAwait(false);
  499. }
  500. }
  501. catch (Exception ex)
  502. {
  503. throw ex as CsvHelperException ?? new WriterException(context, "An unexpected error occurred.", ex);
  504. }
  505. }
  506. #if !NET45
  507. /// <inheritdoc/>
  508. public virtual async Task WriteRecordsAsync<T>(IAsyncEnumerable<T> records, CancellationToken cancellationToken = default)
  509. {
  510. // These methods should all be the same;
  511. // - WriteRecordsAsync(IEnumerable records)
  512. // - WriteRecordsAsync<T>(IEnumerable<T> records)
  513. // - WriteRecordsAsync<T>(IAsyncEnumerable<T> records)
  514. try
  515. {
  516. // Write the header. If records is a List<dynamic>, the header won't be written.
  517. // This is because typeof( T ) = Object.
  518. var recordType = typeof(T);
  519. var isPrimitive = recordType.GetTypeInfo().IsPrimitive;
  520. if (hasHeaderRecord && !hasHeaderBeenWritten && !isPrimitive && recordType != typeof(object))
  521. {
  522. WriteHeader(recordType);
  523. if (hasHeaderBeenWritten)
  524. {
  525. await NextRecordAsync().ConfigureAwait(false);
  526. }
  527. }
  528. var getRecordType = recordType == typeof(object);
  529. await foreach (var record in records)
  530. {
  531. cancellationToken.ThrowIfCancellationRequested();
  532. if (getRecordType)
  533. {
  534. recordType = record.GetType();
  535. }
  536. if (record is IDynamicMetaObjectProvider dynamicObject)
  537. {
  538. if (hasHeaderRecord && !hasHeaderBeenWritten)
  539. {
  540. WriteDynamicHeader(dynamicObject);
  541. await NextRecordAsync().ConfigureAwait(false);
  542. }
  543. }
  544. else
  545. {
  546. // If records is a List<dynamic>, the header hasn't been written yet.
  547. // Write the header based on the record type.
  548. isPrimitive = recordType.GetTypeInfo().IsPrimitive;
  549. if (hasHeaderRecord && !hasHeaderBeenWritten && !isPrimitive)
  550. {
  551. WriteHeader(recordType);
  552. await NextRecordAsync().ConfigureAwait(false);
  553. }
  554. }
  555. try
  556. {
  557. recordManager.Value.Write(record);
  558. }
  559. catch (TargetInvocationException ex)
  560. {
  561. throw ex.InnerException;
  562. }
  563. await NextRecordAsync().ConfigureAwait(false);
  564. }
  565. }
  566. catch (Exception ex)
  567. {
  568. throw ex as CsvHelperException ?? new WriterException(context, "An unexpected error occurred.", ex);
  569. }
  570. }
  571. #endif
  572. /// <inheritdoc/>
  573. public virtual void NextRecord()
  574. {
  575. WriteToBuffer(newLine);
  576. FlushBuffer();
  577. index = 0;
  578. row++;
  579. }
  580. /// <inheritdoc/>
  581. public virtual async Task NextRecordAsync()
  582. {
  583. WriteToBuffer(newLine);
  584. await FlushBufferAsync();
  585. index = 0;
  586. row++;
  587. }
  588. /// <inheritdoc/>
  589. public virtual void Flush()
  590. {
  591. FlushBuffer();
  592. writer.Flush();
  593. }
  594. /// <inheritdoc/>
  595. public virtual async Task FlushAsync()
  596. {
  597. await FlushBufferAsync().ConfigureAwait(false);
  598. await writer.FlushAsync().ConfigureAwait(false);
  599. }
  600. /// <inheritdoc/>
  601. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  602. protected virtual void FlushBuffer()
  603. {
  604. writer.Write(buffer, 0, bufferPosition);
  605. bufferPosition = 0;
  606. }
  607. /// <inheritdoc/>
  608. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  609. protected virtual async Task FlushBufferAsync()
  610. {
  611. await writer.WriteAsync(buffer, 0, bufferPosition);
  612. bufferPosition = 0;
  613. }
  614. /// <inheritdoc/>
  615. public virtual bool CanWrite(MemberMap memberMap)
  616. {
  617. var cantWrite =
  618. // Ignored members.
  619. memberMap.Data.Ignore;
  620. if (memberMap.Data.Member is PropertyInfo property)
  621. {
  622. cantWrite = cantWrite ||
  623. // Properties that don't have a public getter
  624. // and we are honoring the accessor modifier.
  625. property.GetGetMethod() == null && !includePrivateMembers ||
  626. // Properties that don't have a getter at all.
  627. property.GetGetMethod(true) == null;
  628. }
  629. return !cantWrite;
  630. }
  631. /// <inheritdoc/>
  632. public virtual Type GetTypeForRecord<T>(T record)
  633. {
  634. var type = typeof(T);
  635. if (type == typeof(object))
  636. {
  637. type = record.GetType();
  638. }
  639. return type;
  640. }
  641. /// <inheritdoc/>
  642. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  643. protected virtual string SanitizeForInjection(string field)
  644. {
  645. if (string.IsNullOrEmpty(field))
  646. {
  647. return field;
  648. }
  649. if (ArrayHelper.Contains(injectionCharacters, field[0]))
  650. {
  651. return injectionEscapeCharacter + field;
  652. }
  653. if (field[0] == quote && ArrayHelper.Contains(injectionCharacters, field[1]))
  654. {
  655. return field[0].ToString() + injectionEscapeCharacter.ToString() + field.Substring(1);
  656. }
  657. return field;
  658. }
  659. /// <inheritdoc/>
  660. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  661. protected void WriteToBuffer(string value)
  662. {
  663. var length = value?.Length ?? 0;
  664. if (value == null || length == 0)
  665. {
  666. return;
  667. }
  668. var lengthNeeded = bufferPosition + length;
  669. if (lengthNeeded >= bufferSize)
  670. {
  671. while (lengthNeeded >= bufferSize)
  672. {
  673. bufferSize *= 2;
  674. }
  675. Array.Resize(ref buffer, bufferSize);
  676. }
  677. value.CopyTo(0, buffer, bufferPosition, length);
  678. bufferPosition += length;
  679. }
  680. /// <inheritdoc/>
  681. public void Dispose()
  682. {
  683. Dispose(true);
  684. GC.SuppressFinalize(this);
  685. }
  686. /// <inheritdoc/>
  687. protected virtual void Dispose(bool disposing)
  688. {
  689. if (disposed)
  690. {
  691. return;
  692. }
  693. Flush();
  694. if (disposing)
  695. {
  696. // Dispose managed state (managed objects)
  697. if (!leaveOpen)
  698. {
  699. writer.Dispose();
  700. }
  701. }
  702. // Free unmanaged resources (unmanaged objects) and override finalizer
  703. // Set large fields to null
  704. buffer = null;
  705. disposed = true;
  706. }
  707. #if !NET45 && !NET47 && !NETSTANDARD2_0
  708. /// <inheritdoc/>
  709. public async ValueTask DisposeAsync()
  710. {
  711. await DisposeAsync(true).ConfigureAwait(false);
  712. GC.SuppressFinalize(this);
  713. }
  714. /// <inheritdoc/>
  715. protected virtual async ValueTask DisposeAsync(bool disposing)
  716. {
  717. if (disposed)
  718. {
  719. return;
  720. }
  721. await FlushAsync().ConfigureAwait(false);
  722. if (disposing)
  723. {
  724. // Dispose managed state (managed objects)
  725. if (!leaveOpen)
  726. {
  727. await writer.DisposeAsync().ConfigureAwait(false);
  728. }
  729. }
  730. // Free unmanaged resources (unmanaged objects) and override finalizer
  731. // Set large fields to null
  732. buffer = null;
  733. disposed = true;
  734. }
  735. #endif
  736. }
  737. }