-
Notifications
You must be signed in to change notification settings - Fork 5.6k
Expand file tree
/
Copy pathEncodingExtensions.cs
More file actions
587 lines (488 loc) · 32.4 KB
/
Copy pathEncodingExtensions.cs
File metadata and controls
587 lines (488 loc) · 32.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics;
namespace System.Text
{
public static class EncodingExtensions
{
/// <summary>
/// The maximum number of input elements after which we'll begin to chunk the input.
/// </summary>
/// <remarks>
/// The reason for this chunking is that the existing Encoding / Encoder / Decoder APIs
/// like GetByteCount / GetCharCount will throw if an integer overflow occurs. Since
/// we may be working with large inputs in these extension methods, we don't want to
/// risk running into this issue. While it's technically possible even for 1 million
/// input elements to result in an overflow condition, such a scenario is unrealistic,
/// so we won't worry about it.
/// </remarks>
private const int MaxInputElementsPerIteration = 1 * 1024 * 1024;
/// <summary>
/// Encodes the specified <see cref="ReadOnlySpan{Char}"/> to <see langword="byte"/>s using the specified <see cref="Encoding"/>
/// and writes the result to <paramref name="writer"/>.
/// </summary>
/// <param name="encoding">The <see cref="Encoding"/> which represents how the data in <paramref name="chars"/> should be encoded.</param>
/// <param name="chars">The <see cref="ReadOnlySpan{Char}"/> to encode to <see langword="byte"/>s.</param>
/// <param name="writer">The buffer to which the encoded bytes will be written.</param>
/// <exception cref="EncoderFallbackException">Thrown if <paramref name="chars"/> contains data that cannot be encoded and <paramref name="encoding"/> is configured
/// to throw an exception when such data is seen.</exception>
public static long GetBytes(this Encoding encoding, ReadOnlySpan<char> chars, IBufferWriter<byte> writer)
{
ArgumentNullException.ThrowIfNull(encoding);
ArgumentNullException.ThrowIfNull(writer);
if (chars.Length <= MaxInputElementsPerIteration)
{
// The input span is small enough where we can one-shot this.
int byteCount = encoding.GetByteCount(chars);
Span<byte> scratchBuffer = writer.GetSpan(byteCount);
int actualBytesWritten = encoding.GetBytes(chars, scratchBuffer);
writer.Advance(actualBytesWritten);
return actualBytesWritten;
}
else
{
// Allocate a stateful Encoder instance and chunk this.
Convert(encoding.GetEncoder(), chars, writer, flush: true, out long totalBytesWritten, out _);
return totalBytesWritten;
}
}
/// <summary>
/// Decodes the specified <see cref="ReadOnlySequence{Char}"/> to <see langword="byte"/>s using the specified <see cref="Encoding"/>
/// and writes the result to <paramref name="writer"/>.
/// </summary>
/// <param name="encoding">The <see cref="Encoding"/> which represents how the data in <paramref name="chars"/> should be encoded.</param>
/// <param name="chars">The <see cref="ReadOnlySequence{Char}"/> whose contents should be encoded.</param>
/// <param name="writer">The buffer to which the encoded bytes will be written.</param>
/// <returns>The number of bytes written to <paramref name="writer"/>.</returns>
/// <exception cref="EncoderFallbackException">Thrown if <paramref name="chars"/> contains data that cannot be encoded and <paramref name="encoding"/> is configured
/// to throw an exception when such data is seen.</exception>
public static long GetBytes(this Encoding encoding, in ReadOnlySequence<char> chars, IBufferWriter<byte> writer)
{
ArgumentNullException.ThrowIfNull(encoding);
ArgumentNullException.ThrowIfNull(writer);
// Delegate to the Span-based method if possible.
// If that doesn't work, allocate the Encoder instance and run a loop.
if (chars.IsSingleSegment)
{
return GetBytes(encoding, chars.FirstSpan, writer);
}
else
{
Convert(encoding.GetEncoder(), chars, writer, flush: true, out long bytesWritten, out _);
return bytesWritten;
}
}
/// <summary>
/// Encodes the specified <see cref="ReadOnlySequence{Char}"/> to <see langword="byte"/>s using the specified <see cref="Encoding"/>
/// and outputs the result to <paramref name="bytes"/>.
/// </summary>
/// <param name="encoding">The <see cref="Encoding"/> which represents how the data in <paramref name="chars"/> should be encoded.</param>
/// <param name="chars">The <see cref="ReadOnlySequence{Char}"/> to encode to <see langword="byte"/>s.</param>
/// <param name="bytes">The destination buffer to which the encoded bytes will be written.</param>
/// <returns>The number of bytes written to <paramref name="bytes"/>.</returns>
/// <exception cref="ArgumentException">Thrown if <paramref name="bytes"/> is not large enough to contain the encoded form of <paramref name="chars"/>.</exception>
/// <exception cref="EncoderFallbackException">Thrown if <paramref name="chars"/> contains data that cannot be encoded and <paramref name="encoding"/> is configured
/// to throw an exception when such data is seen.</exception>
public static int GetBytes(this Encoding encoding, in ReadOnlySequence<char> chars, Span<byte> bytes)
{
ArgumentNullException.ThrowIfNull(encoding);
if (chars.IsSingleSegment)
{
// If the incoming sequence is single-segment, one-shot this.
return encoding.GetBytes(chars.FirstSpan, bytes);
}
else
{
// If the incoming sequence is multi-segment, create a stateful Encoder
// and use it as the workhorse. On the final iteration we'll pass flush=true.
ReadOnlySequence<char> remainingChars = chars;
int originalBytesLength = bytes.Length;
Encoder encoder = encoding.GetEncoder();
bool isFinalSegment;
do
{
remainingChars.GetFirstSpan(out ReadOnlySpan<char> firstSpan, out SequencePosition next);
isFinalSegment = remainingChars.IsSingleSegment;
int bytesWrittenJustNow = encoder.GetBytes(firstSpan, bytes, flush: isFinalSegment);
bytes = bytes.Slice(bytesWrittenJustNow);
remainingChars = remainingChars.Slice(next);
} while (!isFinalSegment);
return originalBytesLength - bytes.Length; // total number of bytes we wrote
}
}
/// <summary>
/// Encodes the specified <see cref="ReadOnlySequence{Char}"/> into a <see cref="byte"/> array using the specified <see cref="Encoding"/>.
/// </summary>
/// <param name="encoding">The <see cref="Encoding"/> which represents how the data in <paramref name="chars"/> should be encoded.</param>
/// <param name="chars">The <see cref="ReadOnlySequence{Char}"/> to encode to <see langword="byte"/>s.</param>
/// <returns>A <see cref="byte"/> array which represents the encoded contents of <paramref name="chars"/>.</returns>
/// <exception cref="EncoderFallbackException">Thrown if <paramref name="chars"/> contains data that cannot be encoded and <paramref name="encoding"/> is configured
/// to throw an exception when such data is seen.</exception>
public static byte[] GetBytes(this Encoding encoding, in ReadOnlySequence<char> chars)
{
ArgumentNullException.ThrowIfNull(encoding);
if (chars.IsSingleSegment)
{
// If the incoming sequence is single-segment, one-shot this.
ReadOnlySpan<char> span = chars.FirstSpan;
byte[] retVal = new byte[encoding.GetByteCount(span)];
encoding.GetBytes(span, retVal);
return retVal;
}
else
{
// If the incoming sequence is multi-segment, create a stateful Encoder
// and use it as the workhorse. On the final iteration we'll pass flush=true.
Encoder encoder = encoding.GetEncoder();
// Maintain a list of all the segments we'll need to concat together.
// These will be released back to the pool at the end of the method.
List<(byte[], int)> listOfSegments = new List<(byte[], int)>();
int totalByteCount = 0;
ReadOnlySequence<char> remainingChars = chars;
bool isFinalSegment;
do
{
remainingChars.GetFirstSpan(out ReadOnlySpan<char> firstSpan, out SequencePosition next);
isFinalSegment = remainingChars.IsSingleSegment;
int byteCountThisIteration = encoder.GetByteCount(firstSpan, flush: isFinalSegment);
byte[] rentedArray = ArrayPool<byte>.Shared.Rent(byteCountThisIteration);
int actualBytesWrittenThisIteration = encoder.GetBytes(firstSpan, rentedArray, flush: isFinalSegment); // could throw ArgumentException if overflow would occur
listOfSegments.Add((rentedArray, actualBytesWrittenThisIteration));
totalByteCount += actualBytesWrittenThisIteration;
if (totalByteCount < 0)
{
// If we overflowed, call the array ctor, passing int.MaxValue.
// This will end up throwing the expected OutOfMemoryException
// since arrays are limited to under int.MaxValue elements in length.
totalByteCount = int.MaxValue;
break;
}
remainingChars = remainingChars.Slice(next);
} while (!isFinalSegment);
// Now build up the byte[] to return, then release all of our scratch buffers
// back to the shared pool.
byte[] retVal = new byte[totalByteCount];
Span<byte> remainingBytes = retVal;
foreach ((byte[] array, int length) in listOfSegments)
{
array.AsSpan(0, length).CopyTo(remainingBytes);
ArrayPool<byte>.Shared.Return(array);
remainingBytes = remainingBytes.Slice(length);
}
Debug.Assert(remainingBytes.IsEmpty, "Over-allocated the byte[] instance?");
return retVal;
}
}
/// <summary>
/// Decodes the specified <see cref="ReadOnlySpan{Byte}"/> to <see langword="char"/>s using the specified <see cref="Encoding"/>
/// and writes the result to <paramref name="writer"/>.
/// </summary>
/// <param name="encoding">The <see cref="Encoding"/> which represents how the data in <paramref name="bytes"/> should be decoded.</param>
/// <param name="bytes">The <see cref="ReadOnlySpan{Byte}"/> whose bytes should be decoded.</param>
/// <param name="writer">The buffer to which the decoded chars will be written.</param>
/// <returns>The number of chars written to <paramref name="writer"/>.</returns>
/// <exception cref="DecoderFallbackException">Thrown if <paramref name="bytes"/> contains data that cannot be decoded and <paramref name="encoding"/> is configured
/// to throw an exception when such data is seen.</exception>
public static long GetChars(this Encoding encoding, ReadOnlySpan<byte> bytes, IBufferWriter<char> writer)
{
ArgumentNullException.ThrowIfNull(encoding);
ArgumentNullException.ThrowIfNull(writer);
if (bytes.Length <= MaxInputElementsPerIteration)
{
// The input span is small enough where we can one-shot this.
int charCount = encoding.GetCharCount(bytes);
Span<char> scratchBuffer = writer.GetSpan(charCount);
int actualCharsWritten = encoding.GetChars(bytes, scratchBuffer);
writer.Advance(actualCharsWritten);
return actualCharsWritten;
}
else
{
// Allocate a stateful Decoder instance and chunk this.
Convert(encoding.GetDecoder(), bytes, writer, flush: true, out long totalCharsWritten, out _);
return totalCharsWritten;
}
}
/// <summary>
/// Decodes the specified <see cref="ReadOnlySequence{Byte}"/> to <see langword="char"/>s using the specified <see cref="Encoding"/>
/// and writes the result to <paramref name="writer"/>.
/// </summary>
/// <param name="encoding">The <see cref="Encoding"/> which represents how the data in <paramref name="bytes"/> should be decoded.</param>
/// <param name="bytes">The <see cref="ReadOnlySequence{Byte}"/> whose bytes should be decoded.</param>
/// <param name="writer">The buffer to which the decoded chars will be written.</param>
/// <returns>The number of chars written to <paramref name="writer"/>.</returns>
/// <exception cref="DecoderFallbackException">Thrown if <paramref name="bytes"/> contains data that cannot be decoded and <paramref name="encoding"/> is configured
/// to throw an exception when such data is seen.</exception>
public static long GetChars(this Encoding encoding, in ReadOnlySequence<byte> bytes, IBufferWriter<char> writer)
{
ArgumentNullException.ThrowIfNull(encoding);
ArgumentNullException.ThrowIfNull(writer);
// Delegate to the Span-based method if possible.
// If that doesn't work, allocate the Encoder instance and run a loop.
if (bytes.IsSingleSegment)
{
return GetChars(encoding, bytes.FirstSpan, writer);
}
else
{
Convert(encoding.GetDecoder(), bytes, writer, flush: true, out long charsWritten, out _);
return charsWritten;
}
}
/// <summary>
/// Decodes the specified <see cref="ReadOnlySequence{Byte}"/> to <see langword="char"/>s using the specified <see cref="Encoding"/>
/// and outputs the result to <paramref name="chars"/>.
/// </summary>
/// <param name="encoding">The <see cref="Encoding"/> which represents how the data in <paramref name="bytes"/> is encoded.</param>
/// <param name="bytes">The <see cref="ReadOnlySequence{Byte}"/> to decode to characters.</param>
/// <param name="chars">The destination buffer to which the decoded characters will be written.</param>
/// <returns>The number of chars written to <paramref name="chars"/>.</returns>
/// <exception cref="ArgumentException">Thrown if <paramref name="chars"/> is not large enough to contain the encoded form of <paramref name="bytes"/>.</exception>
/// <exception cref="DecoderFallbackException">Thrown if <paramref name="bytes"/> contains data that cannot be decoded and <paramref name="encoding"/> is configured
/// to throw an exception when such data is seen.</exception>
public static int GetChars(this Encoding encoding, in ReadOnlySequence<byte> bytes, Span<char> chars)
{
ArgumentNullException.ThrowIfNull(encoding);
if (bytes.IsSingleSegment)
{
// If the incoming sequence is single-segment, one-shot this.
return encoding.GetChars(bytes.FirstSpan, chars);
}
else
{
// If the incoming sequence is multi-segment, create a stateful Decoder
// and use it as the workhorse. On the final iteration we'll pass flush=true.
ReadOnlySequence<byte> remainingBytes = bytes;
int originalCharsLength = chars.Length;
Decoder decoder = encoding.GetDecoder();
bool isFinalSegment;
do
{
remainingBytes.GetFirstSpan(out ReadOnlySpan<byte> firstSpan, out SequencePosition next);
isFinalSegment = remainingBytes.IsSingleSegment;
int charsWrittenJustNow = decoder.GetChars(firstSpan, chars, flush: isFinalSegment);
chars = chars.Slice(charsWrittenJustNow);
remainingBytes = remainingBytes.Slice(next);
} while (!isFinalSegment);
return originalCharsLength - chars.Length; // total number of chars we wrote
}
}
/// <summary>
/// Decodes the specified <see cref="ReadOnlySequence{Byte}"/> into a <see cref="string"/> using the specified <see cref="Encoding"/>.
/// </summary>
/// <param name="encoding">The <see cref="Encoding"/> which represents how the data in <paramref name="bytes"/> is encoded.</param>
/// <param name="bytes">The <see cref="ReadOnlySequence{Byte}"/> to decode into characters.</param>
/// <returns>A <see cref="string"/> which represents the decoded contents of <paramref name="bytes"/>.</returns>
/// <exception cref="DecoderFallbackException">Thrown if <paramref name="bytes"/> contains data that cannot be decoded and <paramref name="encoding"/> is configured
/// to throw an exception when such data is seen.</exception>
public static string GetString(this Encoding encoding, in ReadOnlySequence<byte> bytes)
{
ArgumentNullException.ThrowIfNull(encoding);
if (bytes.IsSingleSegment)
{
// If the incoming sequence is single-segment, one-shot this.
return encoding.GetString(bytes.FirstSpan);
}
else
{
// If the incoming sequence is multi-segment, create a stateful Decoder
// and use it as the workhorse. On the final iteration we'll pass flush=true.
Decoder decoder = encoding.GetDecoder();
// Maintain a list of all the segments we'll need to concat together.
// These will be released back to the pool at the end of the method.
List<(char[], int)> listOfSegments = new List<(char[], int)>();
int totalCharCount = 0;
ReadOnlySequence<byte> remainingBytes = bytes;
bool isFinalSegment;
do
{
remainingBytes.GetFirstSpan(out ReadOnlySpan<byte> firstSpan, out SequencePosition next);
isFinalSegment = remainingBytes.IsSingleSegment;
int charCountThisIteration = decoder.GetCharCount(firstSpan, flush: isFinalSegment); // could throw ArgumentException if overflow would occur
char[] rentedArray = ArrayPool<char>.Shared.Rent(charCountThisIteration);
int actualCharsWrittenThisIteration = decoder.GetChars(firstSpan, rentedArray, flush: isFinalSegment);
listOfSegments.Add((rentedArray, actualCharsWrittenThisIteration));
totalCharCount += actualCharsWrittenThisIteration;
if (totalCharCount < 0)
{
// If we overflowed, call string.Create, passing int.MaxValue.
// This will end up throwing the expected OutOfMemoryException
// since strings are limited to under int.MaxValue elements in length.
totalCharCount = int.MaxValue;
break;
}
remainingBytes = remainingBytes.Slice(next);
} while (!isFinalSegment);
// Now build up the string to return, then release all of our scratch buffers
// back to the shared pool.
return string.Create(totalCharCount, listOfSegments, (span, listOfSegments) =>
{
foreach ((char[] array, int length) in listOfSegments)
{
array.AsSpan(0, length).CopyTo(span);
ArrayPool<char>.Shared.Return(array);
span = span.Slice(length);
}
Debug.Assert(span.IsEmpty, "Over-allocated the string instance?");
});
}
}
/// <summary>
/// Converts a <see cref="ReadOnlySpan{Char}"/> to bytes using <paramref name="encoder"/> and writes the result to <paramref name="writer"/>.
/// </summary>
/// <param name="encoder">The <see cref="Encoder"/> instance which can convert <see langword="char"/>s to <see langword="byte"/>s.</param>
/// <param name="chars">A sequence of characters to encode.</param>
/// <param name="writer">The buffer to which the encoded bytes will be written.</param>
/// <param name="flush"><see langword="true"/> to indicate no further data is to be converted; otherwise <see langword="false"/>.</param>
/// <param name="bytesUsed">When this method returns, contains the count of <see langword="byte"/>s which were written to <paramref name="writer"/>.</param>
/// <param name="completed">
/// When this method returns, contains <see langword="true"/> if <paramref name="encoder"/> contains no partial internal state; otherwise, <see langword="false"/>.
/// If <paramref name="flush"/> is <see langword="true"/>, this will always be set to <see langword="true"/> when the method returns.
/// </param>
/// <exception cref="EncoderFallbackException">Thrown if <paramref name="chars"/> contains data that cannot be encoded and <paramref name="encoder"/> is configured
/// to throw an exception when such data is seen.</exception>
public static void Convert(this Encoder encoder, ReadOnlySpan<char> chars, IBufferWriter<byte> writer, bool flush, out long bytesUsed, out bool completed)
{
ArgumentNullException.ThrowIfNull(encoder);
ArgumentNullException.ThrowIfNull(writer);
// We need to perform at least one iteration of the loop since the encoder could have internal state.
long totalBytesWritten = 0;
do
{
// If our remaining input is very large, instead truncate it and tell the encoder
// that there'll be more data after this call. This truncation is only for the
// purposes of getting the required byte count. Since the writer may give us a span
// larger than what we asked for, we'll pass the entirety of the remaining data
// to the transcoding routine, since it may be able to make progress beyond what
// was initially computed for the truncated input data.
int byteCountForThisSlice = (chars.Length <= MaxInputElementsPerIteration)
? encoder.GetByteCount(chars, flush)
: encoder.GetByteCount(chars.Slice(0, MaxInputElementsPerIteration), flush: false /* this isn't the end of the data */);
Span<byte> scratchBuffer = writer.GetSpan(byteCountForThisSlice);
encoder.Convert(chars, scratchBuffer, flush, out int charsUsedJustNow, out int bytesWrittenJustNow, out completed);
chars = chars.Slice(charsUsedJustNow);
writer.Advance(bytesWrittenJustNow);
totalBytesWritten += bytesWrittenJustNow;
} while (!chars.IsEmpty);
bytesUsed = totalBytesWritten;
}
/// <summary>
/// Converts a <see cref="ReadOnlySequence{Char}"/> to encoded bytes and writes the result to <paramref name="writer"/>.
/// </summary>
/// <param name="encoder">The <see cref="Encoder"/> instance which can convert <see langword="char"/>s to <see langword="byte"/>s.</param>
/// <param name="chars">A sequence of characters to encode.</param>
/// <param name="writer">The buffer to which the encoded bytes will be written.</param>
/// <param name="flush"><see langword="true"/> to indicate no further data is to be converted; otherwise <see langword="false"/>.</param>
/// <param name="bytesUsed">When this method returns, contains the count of <see langword="byte"/>s which were written to <paramref name="writer"/>.</param>
/// <param name="completed">When this method returns, contains <see langword="true"/> if all input up until <paramref name="bytesUsed"/> was
/// converted; otherwise, <see langword="false"/>. If <paramref name="flush"/> is <see langword="true"/>, this will always be set to
/// <see langword="true"/> when the method returns.</param>
/// <exception cref="EncoderFallbackException">Thrown if <paramref name="chars"/> contains data that cannot be encoded and <paramref name="encoder"/> is configured
/// to throw an exception when such data is seen.</exception>
public static void Convert(this Encoder encoder, in ReadOnlySequence<char> chars, IBufferWriter<byte> writer, bool flush, out long bytesUsed, out bool completed)
{
// Parameter null checks will be performed by the workhorse routine.
if (chars.IsSingleSegment)
{
Convert(encoder, chars.FirstSpan, writer, flush, out bytesUsed, out completed);
}
else
{
ReadOnlySequence<char> remainingChars = chars;
long totalBytesWritten = 0;
bool isFinalSegment;
do
{
// Process each segment individually. We need to run at least one iteration of the loop in case
// the Encoder has internal state.
remainingChars.GetFirstSpan(out ReadOnlySpan<char> firstSpan, out SequencePosition next);
isFinalSegment = remainingChars.IsSingleSegment;
Convert(encoder, firstSpan, writer, flush && isFinalSegment, out long bytesWrittenThisIteration, out completed);
totalBytesWritten += bytesWrittenThisIteration;
remainingChars = remainingChars.Slice(next);
} while (!isFinalSegment);
bytesUsed = totalBytesWritten;
}
}
/// <summary>
/// Converts a <see cref="ReadOnlySpan{Byte}"/> to chars using <paramref name="decoder"/> and writes the result to <paramref name="writer"/>.
/// </summary>
/// <param name="decoder">The <see cref="Decoder"/> instance which can convert <see langword="byte"/>s to <see langword="char"/>s.</param>
/// <param name="bytes">A sequence of bytes to decode.</param>
/// <param name="writer">The buffer to which the decoded chars will be written.</param>
/// <param name="flush"><see langword="true"/> to indicate no further data is to be converted; otherwise <see langword="false"/>.</param>
/// <param name="charsUsed">When this method returns, contains the count of <see langword="char"/>s which were written to <paramref name="writer"/>.</param>
/// <param name="completed">
/// When this method returns, contains <see langword="true"/> if <paramref name="decoder"/> contains no partial internal state; otherwise, <see langword="false"/>.
/// If <paramref name="flush"/> is <see langword="true"/>, this will always be set to <see langword="true"/> when the method returns.
/// </param>
/// <exception cref="DecoderFallbackException">Thrown if <paramref name="bytes"/> contains data that cannot be encoded and <paramref name="decoder"/> is configured
/// to throw an exception when such data is seen.</exception>
public static void Convert(this Decoder decoder, ReadOnlySpan<byte> bytes, IBufferWriter<char> writer, bool flush, out long charsUsed, out bool completed)
{
ArgumentNullException.ThrowIfNull(decoder);
ArgumentNullException.ThrowIfNull(writer);
// We need to perform at least one iteration of the loop since the decoder could have internal state.
long totalCharsWritten = 0;
do
{
// If our remaining input is very large, instead truncate it and tell the decoder
// that there'll be more data after this call. This truncation is only for the
// purposes of getting the required char count. Since the writer may give us a span
// larger than what we asked for, we'll pass the entirety of the remaining data
// to the transcoding routine, since it may be able to make progress beyond what
// was initially computed for the truncated input data.
int charCountForThisSlice = (bytes.Length <= MaxInputElementsPerIteration)
? decoder.GetCharCount(bytes, flush)
: decoder.GetCharCount(bytes.Slice(0, MaxInputElementsPerIteration), flush: false /* this isn't the end of the data */);
Span<char> scratchBuffer = writer.GetSpan(charCountForThisSlice);
decoder.Convert(bytes, scratchBuffer, flush, out int bytesUsedJustNow, out int charsWrittenJustNow, out completed);
bytes = bytes.Slice(bytesUsedJustNow);
writer.Advance(charsWrittenJustNow);
totalCharsWritten += charsWrittenJustNow;
} while (!bytes.IsEmpty);
charsUsed = totalCharsWritten;
}
/// <summary>
/// Converts a <see cref="ReadOnlySequence{Byte}"/> to UTF-16 encoded characters and writes the result to <paramref name="writer"/>.
/// </summary>
/// <param name="decoder">The <see cref="Decoder"/> instance which can convert <see langword="byte"/>s to <see langword="char"/>s.</param>
/// <param name="bytes">A sequence of bytes to decode.</param>
/// <param name="writer">The buffer to which the decoded characters will be written.</param>
/// <param name="flush"><see langword="true"/> to indicate no further data is to be converted; otherwise <see langword="false"/>.</param>
/// <param name="charsUsed">When this method returns, contains the count of <see langword="char"/>s which were written to <paramref name="writer"/>.</param>
/// <param name="completed">
/// When this method returns, contains <see langword="true"/> if <paramref name="decoder"/> contains no partial internal state; otherwise, <see langword="false"/>.
/// If <paramref name="flush"/> is <see langword="true"/>, this will always be set to <see langword="true"/> when the method returns.
/// </param>
/// <exception cref="DecoderFallbackException">Thrown if <paramref name="bytes"/> contains data that cannot be decoded and <paramref name="decoder"/> is configured
/// to throw an exception when such data is seen.</exception>
public static void Convert(this Decoder decoder, in ReadOnlySequence<byte> bytes, IBufferWriter<char> writer, bool flush, out long charsUsed, out bool completed)
{
// Parameter null checks will be performed by the workhorse routine.
if (bytes.IsSingleSegment)
{
Convert(decoder, bytes.FirstSpan, writer, flush, out charsUsed, out completed);
}
else
{
ReadOnlySequence<byte> remainingBytes = bytes;
long totalCharsWritten = 0;
bool isFinalSegment;
do
{
// Process each segment individually. We need to run at least one iteration of the loop in case
// the Decoder has internal state.
remainingBytes.GetFirstSpan(out ReadOnlySpan<byte> firstSpan, out SequencePosition next);
isFinalSegment = remainingBytes.IsSingleSegment;
Convert(decoder, firstSpan, writer, flush && isFinalSegment, out long charsWrittenThisIteration, out completed);
totalCharsWritten += charsWrittenThisIteration;
remainingBytes = remainingBytes.Slice(next);
} while (!isFinalSegment);
charsUsed = totalCharsWritten;
}
}
}
}