Skip to content

Commit 053e17a

Browse files
authored
Implement IUtf8SpanFormattable on Version (#84556)
1 parent 58ea9cb commit 053e17a

File tree

3 files changed

+50
-7
lines changed

3 files changed

+50
-7
lines changed

src/libraries/System.Private.CoreLib/src/System/Version.cs

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Buffers.Text;
45
using System.Diagnostics;
56
using System.Diagnostics.CodeAnalysis;
67
using System.Globalization;
8+
using System.Numerics;
79
using System.Runtime.CompilerServices;
10+
using System.Runtime.InteropServices;
811

912
namespace System
1013
{
@@ -16,7 +19,7 @@ namespace System
1619

1720
[Serializable]
1821
[TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
19-
public sealed class Version : ICloneable, IComparable, IComparable<Version?>, IEquatable<Version?>, ISpanFormattable
22+
public sealed class Version : ICloneable, IComparable, IComparable<Version?>, IEquatable<Version?>, ISpanFormattable, IUtf8SpanFormattable
2023
{
2124
// AssemblyName depends on the order staying the same
2225
private readonly int _Major; // Do not rename (binary serialization)
@@ -177,10 +180,15 @@ string IFormattable.ToString(string? format, IFormatProvider? formatProvider) =>
177180
ToString();
178181

179182
public bool TryFormat(Span<char> destination, out int charsWritten) =>
180-
TryFormat(destination, DefaultFormatFieldCount, out charsWritten);
183+
TryFormatCore(destination, DefaultFormatFieldCount, out charsWritten);
181184

182-
public bool TryFormat(Span<char> destination, int fieldCount, out int charsWritten)
185+
public bool TryFormat(Span<char> destination, int fieldCount, out int charsWritten) =>
186+
TryFormatCore(destination, fieldCount, out charsWritten);
187+
188+
private bool TryFormatCore<TChar>(Span<TChar> destination, int fieldCount, out int charsWritten) where TChar : unmanaged, IBinaryInteger<TChar>
183189
{
190+
Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte));
191+
184192
switch ((uint)fieldCount)
185193
{
186194
case > 4:
@@ -211,7 +219,7 @@ static void ThrowArgumentException(string failureUpperBound) =>
211219
return false;
212220
}
213221

214-
destination[0] = '.';
222+
destination[0] = TChar.CreateTruncating('.');
215223
destination = destination.Slice(1);
216224
totalCharsWritten++;
217225
}
@@ -224,7 +232,12 @@ static void ThrowArgumentException(string failureUpperBound) =>
224232
_ => _Revision
225233
};
226234

227-
if (!((uint)value).TryFormat(destination, out int valueCharsWritten))
235+
int valueCharsWritten;
236+
bool formatted = typeof(TChar) == typeof(char) ?
237+
((uint)value).TryFormat(MemoryMarshal.Cast<TChar, char>(destination), out valueCharsWritten) :
238+
Utf8Formatter.TryFormat((uint)value, MemoryMarshal.Cast<TChar, byte>(destination), out valueCharsWritten); // TODO https://github.com/dotnet/runtime/issues/84527: Use UInt32's IUtf8SpanFormattable when available
239+
240+
if (!formatted)
228241
{
229242
charsWritten = 0;
230243
return false;
@@ -240,7 +253,11 @@ static void ThrowArgumentException(string failureUpperBound) =>
240253

241254
bool ISpanFormattable.TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider? provider) =>
242255
// format and provider are ignored.
243-
TryFormat(destination, DefaultFormatFieldCount, out charsWritten);
256+
TryFormatCore(destination, DefaultFormatFieldCount, out charsWritten);
257+
258+
bool IUtf8SpanFormattable.TryFormat(Span<byte> utf8Destination, out int bytesWritten, ReadOnlySpan<char> format, IFormatProvider? provider) =>
259+
// format and provider are ignored.
260+
TryFormatCore(utf8Destination, DefaultFormatFieldCount, out bytesWritten);
244261

245262
private int DefaultFormatFieldCount =>
246263
_Build == -1 ? 2 :

src/libraries/System.Runtime/ref/System.Runtime.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7058,7 +7058,7 @@ protected ValueType() { }
70587058
public override int GetHashCode() { throw null; }
70597059
public override string? ToString() { throw null; }
70607060
}
7061-
public sealed partial class Version : System.ICloneable, System.IComparable, System.IComparable<System.Version?>, System.IEquatable<System.Version?>, System.IFormattable, System.ISpanFormattable
7061+
public sealed partial class Version : System.ICloneable, System.IComparable, System.IComparable<System.Version?>, System.IEquatable<System.Version?>, System.IFormattable, System.ISpanFormattable, System.IUtf8SpanFormattable
70627062
{
70637063
public Version() { }
70647064
public Version(int major, int minor) { }
@@ -7087,6 +7087,7 @@ public Version(string version) { }
70877087
public static System.Version Parse(string input) { throw null; }
70887088
string System.IFormattable.ToString(string? format, System.IFormatProvider? formatProvider) { throw null; }
70897089
bool System.ISpanFormattable.TryFormat(System.Span<char> destination, out int charsWritten, System.ReadOnlySpan<char> format, System.IFormatProvider? provider) { throw null; }
7090+
bool System.IUtf8SpanFormattable.TryFormat(System.Span<byte> utf8Destination, out int bytesWritten, System.ReadOnlySpan<char> format, System.IFormatProvider? provider) { throw null; }
70907091
public override string ToString() { throw null; }
70917092
public string ToString(int fieldCount) { throw null; }
70927093
public bool TryFormat(System.Span<char> destination, int fieldCount, out int charsWritten) { throw null; }

src/libraries/System.Runtime/tests/System/VersionTests.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Collections.Generic;
5+
using System.Text;
56
using Xunit;
67

78
namespace System.Tests
@@ -405,5 +406,29 @@ public static void TryFormat_Invoke_WritesExpected(Version version, string[] exp
405406
AssertExtensions.Throws<ArgumentException>("fieldCount", () => version.TryFormat(dest, -1, out charsWritten)); // Index < 0
406407
AssertExtensions.Throws<ArgumentException>("fieldCount", () => version.TryFormat(dest, maxFieldCount + 1, out charsWritten)); // Index > version.fieldCount
407408
}
409+
410+
[Theory]
411+
[MemberData(nameof(ToString_TestData))]
412+
public static void IUtf8SpanFormattableTryFormat_Invoke_WritesExpected(Version version, string[] expectedFieldCounts)
413+
{
414+
string expected = expectedFieldCounts[^1];
415+
416+
// Too small
417+
byte[] dest = new byte[expected.Length - 1];
418+
Assert.False(((IUtf8SpanFormattable)version).TryFormat(dest, out int charsWritten, default, null));
419+
Assert.Equal(0, charsWritten);
420+
421+
// Just right
422+
dest = new byte[expected.Length];
423+
Assert.True(((IUtf8SpanFormattable)version).TryFormat(dest, out charsWritten, default, null));
424+
Assert.Equal(expected.Length, charsWritten);
425+
Assert.Equal(expected, Encoding.UTF8.GetString(dest.AsSpan(0, charsWritten)));
426+
427+
// More than needed
428+
dest = new byte[expected.Length + 10];
429+
Assert.True(((IUtf8SpanFormattable)version).TryFormat(dest, out charsWritten, default, null));
430+
Assert.Equal(expected.Length, charsWritten);
431+
Assert.Equal(expected, Encoding.UTF8.GetString(dest.AsSpan(0, charsWritten)));
432+
}
408433
}
409434
}

0 commit comments

Comments
 (0)