Skip to content

Commit

Permalink
Implement IUtf8SpanFormattable on Version (#84556)
Browse files Browse the repository at this point in the history
  • Loading branch information
stephentoub committed Apr 10, 2023
1 parent 58ea9cb commit 053e17a
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 7 deletions.
29 changes: 23 additions & 6 deletions src/libraries/System.Private.CoreLib/src/System/Version.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
// 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.Text;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace System
{
Expand All @@ -16,7 +19,7 @@ namespace System

[Serializable]
[TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
public sealed class Version : ICloneable, IComparable, IComparable<Version?>, IEquatable<Version?>, ISpanFormattable
public sealed class Version : ICloneable, IComparable, IComparable<Version?>, IEquatable<Version?>, ISpanFormattable, IUtf8SpanFormattable
{
// AssemblyName depends on the order staying the same
private readonly int _Major; // Do not rename (binary serialization)
Expand Down Expand Up @@ -177,10 +180,15 @@ public string ToString(int fieldCount)
ToString();

public bool TryFormat(Span<char> destination, out int charsWritten) =>
TryFormat(destination, DefaultFormatFieldCount, out charsWritten);
TryFormatCore(destination, DefaultFormatFieldCount, out charsWritten);

public bool TryFormat(Span<char> destination, int fieldCount, out int charsWritten)
public bool TryFormat(Span<char> destination, int fieldCount, out int charsWritten) =>
TryFormatCore(destination, fieldCount, out charsWritten);

private bool TryFormatCore<TChar>(Span<TChar> destination, int fieldCount, out int charsWritten) where TChar : unmanaged, IBinaryInteger<TChar>
{
Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte));

switch ((uint)fieldCount)
{
case > 4:
Expand Down Expand Up @@ -211,7 +219,7 @@ public bool TryFormat(Span<char> destination, int fieldCount, out int charsWritt
return false;
}

destination[0] = '.';
destination[0] = TChar.CreateTruncating('.');
destination = destination.Slice(1);
totalCharsWritten++;
}
Expand All @@ -224,7 +232,12 @@ public bool TryFormat(Span<char> destination, int fieldCount, out int charsWritt
_ => _Revision
};

if (!((uint)value).TryFormat(destination, out int valueCharsWritten))
int valueCharsWritten;
bool formatted = typeof(TChar) == typeof(char) ?
((uint)value).TryFormat(MemoryMarshal.Cast<TChar, char>(destination), out valueCharsWritten) :
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

if (!formatted)
{
charsWritten = 0;
return false;
Expand All @@ -240,7 +253,11 @@ public bool TryFormat(Span<char> destination, int fieldCount, out int charsWritt

bool ISpanFormattable.TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider? provider) =>
// format and provider are ignored.
TryFormat(destination, DefaultFormatFieldCount, out charsWritten);
TryFormatCore(destination, DefaultFormatFieldCount, out charsWritten);

bool IUtf8SpanFormattable.TryFormat(Span<byte> utf8Destination, out int bytesWritten, ReadOnlySpan<char> format, IFormatProvider? provider) =>
// format and provider are ignored.
TryFormatCore(utf8Destination, DefaultFormatFieldCount, out bytesWritten);

private int DefaultFormatFieldCount =>
_Build == -1 ? 2 :
Expand Down
3 changes: 2 additions & 1 deletion src/libraries/System.Runtime/ref/System.Runtime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7058,7 +7058,7 @@ public abstract partial class ValueType
public override int GetHashCode() { throw null; }
public override string? ToString() { throw null; }
}
public sealed partial class Version : System.ICloneable, System.IComparable, System.IComparable<System.Version?>, System.IEquatable<System.Version?>, System.IFormattable, System.ISpanFormattable
public sealed partial class Version : System.ICloneable, System.IComparable, System.IComparable<System.Version?>, System.IEquatable<System.Version?>, System.IFormattable, System.ISpanFormattable, System.IUtf8SpanFormattable
{
public Version() { }
public Version(int major, int minor) { }
Expand Down Expand Up @@ -7087,6 +7087,7 @@ public sealed partial class Version : System.ICloneable, System.IComparable, Sys
public static System.Version Parse(string input) { throw null; }
string System.IFormattable.ToString(string? format, System.IFormatProvider? formatProvider) { throw null; }
bool System.ISpanFormattable.TryFormat(System.Span<char> destination, out int charsWritten, System.ReadOnlySpan<char> format, System.IFormatProvider? provider) { throw null; }
bool System.IUtf8SpanFormattable.TryFormat(System.Span<byte> utf8Destination, out int bytesWritten, System.ReadOnlySpan<char> format, System.IFormatProvider? provider) { throw null; }
public override string ToString() { throw null; }
public string ToString(int fieldCount) { throw null; }
public bool TryFormat(System.Span<char> destination, int fieldCount, out int charsWritten) { throw null; }
Expand Down
25 changes: 25 additions & 0 deletions src/libraries/System.Runtime/tests/System/VersionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Text;
using Xunit;

namespace System.Tests
Expand Down Expand Up @@ -405,5 +406,29 @@ public static void TryFormat_Invoke_WritesExpected(Version version, string[] exp
AssertExtensions.Throws<ArgumentException>("fieldCount", () => version.TryFormat(dest, -1, out charsWritten)); // Index < 0
AssertExtensions.Throws<ArgumentException>("fieldCount", () => version.TryFormat(dest, maxFieldCount + 1, out charsWritten)); // Index > version.fieldCount
}

[Theory]
[MemberData(nameof(ToString_TestData))]
public static void IUtf8SpanFormattableTryFormat_Invoke_WritesExpected(Version version, string[] expectedFieldCounts)
{
string expected = expectedFieldCounts[^1];

// Too small
byte[] dest = new byte[expected.Length - 1];
Assert.False(((IUtf8SpanFormattable)version).TryFormat(dest, out int charsWritten, default, null));
Assert.Equal(0, charsWritten);

// Just right
dest = new byte[expected.Length];
Assert.True(((IUtf8SpanFormattable)version).TryFormat(dest, out charsWritten, default, null));
Assert.Equal(expected.Length, charsWritten);
Assert.Equal(expected, Encoding.UTF8.GetString(dest.AsSpan(0, charsWritten)));

// More than needed
dest = new byte[expected.Length + 10];
Assert.True(((IUtf8SpanFormattable)version).TryFormat(dest, out charsWritten, default, null));
Assert.Equal(expected.Length, charsWritten);
Assert.Equal(expected, Encoding.UTF8.GetString(dest.AsSpan(0, charsWritten)));
}
}
}

0 comments on commit 053e17a

Please sign in to comment.