From 0010a7b23f972a2cd255af1da5fc388ae51da2fb Mon Sep 17 00:00:00 2001 From: Traian Zaprianov Date: Thu, 29 Sep 2022 19:01:32 +0300 Subject: [PATCH 01/17] Introduce TryFormats for almost all primitive types --- .../src/System/Xml/Schema/XsdDateTime.cs | 20 +- .../src/System/Xml/Schema/XsdDuration.cs | 23 +- .../src/System/Xml/XmlConvert.cs | 232 ++++++++++++++++++ 3 files changed, 269 insertions(+), 6 deletions(-) diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDateTime.cs b/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDateTime.cs index 05adf65c48717..95c0f8d94aa5b 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDateTime.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDateTime.cs @@ -131,6 +131,8 @@ private enum XsdDateTimeKind private static readonly int[] DaysToMonth366 = { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366}; + private const int CharStackBufferSize = 64; + /// /// Constructs an XsdDateTime from a string using specific format. /// @@ -494,7 +496,22 @@ public static implicit operator DateTimeOffset(XsdDateTime xdt) /// public override string ToString() { - var vsb = new ValueStringBuilder(stackalloc char[64]); + var vsb = new ValueStringBuilder(stackalloc char[CharStackBufferSize]); + Format(ref vsb); + + return vsb.ToString(); + } + + public bool TryFormat(Span destination, out int charsWritten) + { + var sb = new ValueStringBuilder(stackalloc char[CharStackBufferSize]); + Format(ref sb); + + return sb.TryCopyTo(destination, out charsWritten); + } + + private void Format(ref ValueStringBuilder vsb) + { switch (InternalTypeCode) { case DateTimeTypeCode.DateTime: @@ -533,7 +550,6 @@ public override string ToString() break; } PrintZone(ref vsb); - return vsb.ToString(); } // Serialize year, month and day diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDuration.cs b/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDuration.cs index 80358d208c796..a32f639b2c4c9 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDuration.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDuration.cs @@ -23,6 +23,7 @@ internal struct XsdDuration private uint _nanoseconds; // High bit is used to indicate whether duration is negative private const uint NegativeBit = 0x80000000; + private const int CharStackBufferSize = 20; private enum Parts { @@ -334,13 +335,28 @@ public override string ToString() return ToString(DurationType.Duration); } + public bool TryFormat(Span destination, out int charsWritten, DurationType durationType = DurationType.Duration) + { + var sb = new ValueStringBuilder(stackalloc char[CharStackBufferSize]); + Format(ref sb, durationType); + + return sb.TryCopyTo(destination, out charsWritten); + } + /// /// Return the string representation according to xsd:duration rules, xdt:dayTimeDuration rules, or /// xdt:yearMonthDuration rules. /// internal string ToString(DurationType durationType) { - var vsb = new ValueStringBuilder(stackalloc char[20]); + var vsb = new ValueStringBuilder(stackalloc char[CharStackBufferSize]); + Format(ref vsb, durationType); + + return vsb.ToString(); + } + + private void Format(ref ValueStringBuilder vsb, DurationType durationType) + { int nanoseconds, digit, zeroIdx, len; if (IsNegative) @@ -410,7 +426,8 @@ internal string ToString(DurationType durationType) } vsb.EnsureCapacity(zeroIdx + 1); - vsb.Append(tmpSpan.Slice(0, zeroIdx - len + 1)); + var nanoSpanLength = zeroIdx - len + 1; + tmpSpan[..nanoSpanLength].TryCopyTo(vsb.AppendSpan(nanoSpanLength)); } vsb.Append('S'); } @@ -426,8 +443,6 @@ internal string ToString(DurationType durationType) if (vsb[vsb.Length - 1] == 'P') vsb.Append("0M"); } - - return vsb.ToString(); } internal static Exception? TryParse(string s, out XsdDuration result) diff --git a/src/libraries/System.Private.Xml/src/System/Xml/XmlConvert.cs b/src/libraries/System.Private.Xml/src/System/Xml/XmlConvert.cs index 760d2a6c2efae..b2c4e1ca9d005 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/XmlConvert.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/XmlConvert.cs @@ -1664,5 +1664,237 @@ internal static Exception CreateInvalidNameCharException(string name, int index, { return CreateException(index == 0 ? SR.Xml_BadStartNameChar : SR.Xml_BadNameChar, XmlException.BuildCharExceptionArgs(name, index), exceptionType, 0, index + 1); } + + internal static bool TryFormat(bool value, Span destination, out int charsWritten) + { + if (value) + { + if (destination.Length < 4) + { + charsWritten = -1; + return false; + } + + destination[0] = 't'; + destination[1] = 'r'; + destination[2] = 'u'; + destination[3] = 'e'; + charsWritten = 4; + + return true; + } + + if (destination.Length < 5) + { + charsWritten = -1; + return false; + } + + destination[0] = 'f'; + destination[1] = 'a'; + destination[2] = 'l'; + destination[3] = 's'; + destination[4] = 'e'; + charsWritten = 5; + return true; + } + + internal static bool TryFormat(char value, Span destination, out int charsWritten) + { + charsWritten = -1; + if (destination.Length < 1) return false; + + destination[0] = value; + charsWritten = 1; + return true; + } + + internal static bool TryFormat(decimal value, Span destination, out int charsWritten) + { + return value.TryFormat(destination, out charsWritten, default, NumberFormatInfo.InvariantInfo); + } + + internal static bool TryFormat(sbyte value, Span destination, out int charsWritten) + { + return value.TryFormat(destination, out charsWritten, default, CultureInfo.InvariantCulture); + } + + internal static bool TryFormat(short value, Span destination, out int charsWritten) + { + return value.TryFormat(destination, out charsWritten, default, CultureInfo.InvariantCulture); + } + + internal static bool TryFormat(int value, Span destination, out int charsWritten) + { + return value.TryFormat(destination, out charsWritten, default, CultureInfo.InvariantCulture); + } + + internal static bool TryFormat(long value, Span destination, out int charsWritten) + { + return value.TryFormat(destination, out charsWritten, default, CultureInfo.InvariantCulture); + } + + internal static bool TryFormat(byte value, Span destination, out int charsWritten) + { + return value.TryFormat(destination, out charsWritten, default, CultureInfo.InvariantCulture); + } + + internal static bool TryFormat(ushort value, Span destination, out int charsWritten) + { + return value.TryFormat(destination, out charsWritten, default, CultureInfo.InvariantCulture); + } + + internal static bool TryFormat(uint value, Span destination, out int charsWritten) + { + return value.TryFormat(destination, out charsWritten, default, CultureInfo.InvariantCulture); + } + + internal static bool TryFormat(ulong value, Span destination, out int charsWritten) + { + return value.TryFormat(destination, out charsWritten, default, CultureInfo.InvariantCulture); + } + + internal static bool TryFormat(float value, Span destination, out int charsWritten) + { + if (float.IsNegativeInfinity(value)) + { + if (destination.Length < 4) + { + charsWritten = -1; + return false; + } + destination[0] = '-'; + destination[1] = 'I'; + destination[2] = 'N'; + destination[3] = 'F'; + charsWritten = 4; + return true; + } + + if (float.IsPositiveInfinity(value)) + { + if (destination.Length < 3) + { + charsWritten = -1; + return false; + } + destination[0] = 'I'; + destination[1] = 'N'; + destination[2] = 'F'; + charsWritten = 3; + return true; + } + if (IsNegativeZero((double)value)) + { + if (destination.Length < 2) + { + charsWritten = -1; + return false; + } + destination[0] = '-'; + destination[1] = '0'; + charsWritten = 2; + return true; + } + return value.TryFormat(destination, out charsWritten, "R", NumberFormatInfo.InvariantInfo); + } + + internal static bool TryFormat(double value, Span destination, out int charsWritten) + { + if (double.IsNegativeInfinity(value)) + { + if (destination.Length < 4) + { + charsWritten = -1; + return false; + } + destination[0] = '-'; + destination[1] = 'I'; + destination[2] = 'N'; + destination[3] = 'F'; + charsWritten = 4; + return true; + } + + if (double.IsPositiveInfinity(value)) + { + if (destination.Length < 3) + { + charsWritten = -1; + return false; + } + destination[0] = 'I'; + destination[1] = 'N'; + destination[2] = 'F'; + charsWritten = 3; + return true; + } + if (IsNegativeZero(value)) + { + if (destination.Length < 2) + { + charsWritten = -1; + return false; + } + destination[0] = '-'; + destination[1] = '0'; + charsWritten = 2; + return true; + } + return value.TryFormat(destination, out charsWritten, "R", NumberFormatInfo.InvariantInfo); + } + + internal static bool TryFormat(TimeSpan value, Span destination, out int charsWritten) + { + return new XsdDuration(value).TryFormat(destination, out charsWritten); + } + + internal static bool TryFormat(DateTime value, Span destination, out int charsWritten) + { + return TryFormat(value, XmlDateTimeSerializationMode.RoundtripKind, destination, out charsWritten); + } + + internal static bool TryFormat(DateTime value, XmlDateTimeSerializationMode dateTimeOption, Span destination, out int charsWritten) + { + switch (dateTimeOption) + { + case XmlDateTimeSerializationMode.Local: + value = SwitchToLocalTime(value); + break; + + case XmlDateTimeSerializationMode.Utc: + value = SwitchToUtcTime(value); + break; + + case XmlDateTimeSerializationMode.Unspecified: + value = new DateTime(value.Ticks, DateTimeKind.Unspecified); + break; + + case XmlDateTimeSerializationMode.RoundtripKind: + break; + + default: + throw new ArgumentException(SR.Format(SR.Sch_InvalidDateTimeOption, dateTimeOption, nameof(dateTimeOption))); + } + + XsdDateTime xsdDateTime = new XsdDateTime(value, XsdDateTimeFlags.DateTime); + return xsdDateTime.TryFormat(destination, out charsWritten); + } + + internal static bool TryFormat(DateTimeOffset value, Span destination, out int charsWritten) + { + XsdDateTime xsdDateTime = new XsdDateTime(value); + return xsdDateTime.TryFormat(destination, out charsWritten); + } + + internal static bool TryFormat(DateTimeOffset value, [StringSyntax(StringSyntaxAttribute.DateTimeFormat)] string format, Span destination, out int charsWritten) + { + return value.TryFormat(destination, out charsWritten, format, DateTimeFormatInfo.InvariantInfo); + } + + internal static bool TryFormat(Guid value, Span destination, out int charsWritten) + { + return value.TryFormat(destination, out charsWritten); + } } } From 7a773e18d61811bbe133bba534e8d2a6932567af Mon Sep 17 00:00:00 2001 From: Trayan Zapryanov Date: Thu, 29 Sep 2022 21:24:39 +0300 Subject: [PATCH 02/17] Use primitive char buffer in XmlSerializationWriter --- .../Serialization/XmlSerializationWriter.cs | 69 ++++++++++++------- .../Xml/Serialization/Xmlcustomformatter.cs | 11 +++ .../src/System/Xml/XmlConvert.cs | 5 ++ 3 files changed, 59 insertions(+), 26 deletions(-) diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriter.cs b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriter.cs index ed849b76284ee..2e98410fb28b7 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriter.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriter.cs @@ -38,6 +38,9 @@ public abstract class XmlSerializationWriter : XmlSerializationGeneratedCode private bool _soap12; private bool _escapeName = true; + //char buffer for serializing primitive values + private readonly char[] _primitivesBuffer = new char[128]; + // this method must be called before any generated serialization methods are called internal void Init(XmlWriter w, XmlSerializerNamespaces? namespaces, string? encodingStyle, string? idBase, TempAssembly? tempAssembly) { @@ -120,6 +123,11 @@ protected static string FromDateTime(DateTime value) return XmlCustomFormatter.FromDateTime(value); } + internal static bool TryFormatDateTime(DateTime value, Span destination, out int charsWritten) + { + return XmlCustomFormatter.TryFormatDateTime(value, destination, out charsWritten); + } + protected static string FromDate(DateTime value) { return XmlCustomFormatter.FromDate(value); @@ -246,13 +254,15 @@ private XmlQualifiedName GetPrimitiveTypeName(Type type) [RequiresUnreferencedCode(XmlSerializer.TrimSerializationWarning)] protected void WriteTypedPrimitive(string? name, string? ns, object o, bool xsiType) { - string? value; + string? value = null; string type; string typeNs = XmlSchema.Namespace; bool writeRaw = true; bool writeDirect = false; Type t = o.GetType(); bool wroteStartElement = false; + bool? tryFormatResult = null; + int charsWritten = -1; switch (Type.GetTypeCode(t)) { @@ -262,60 +272,60 @@ protected void WriteTypedPrimitive(string? name, string? ns, object o, bool xsiT writeRaw = false; break; case TypeCode.Int32: - value = XmlConvert.ToString((int)o); + tryFormatResult = XmlConvert.TryFormat((int)o, _primitivesBuffer, out charsWritten); type = "int"; break; case TypeCode.Boolean: - value = XmlConvert.ToString((bool)o); + tryFormatResult = XmlConvert.TryFormat((bool)o, _primitivesBuffer, out charsWritten); type = "boolean"; break; case TypeCode.Int16: - value = XmlConvert.ToString((short)o); + tryFormatResult = XmlConvert.TryFormat((short)o, _primitivesBuffer, out charsWritten); type = "short"; break; case TypeCode.Int64: - value = XmlConvert.ToString((long)o); + tryFormatResult = XmlConvert.TryFormat((long)o, _primitivesBuffer, out charsWritten); type = "long"; break; case TypeCode.Single: - value = XmlConvert.ToString((float)o); + tryFormatResult = XmlConvert.TryFormat((float)o, _primitivesBuffer, out charsWritten); type = "float"; break; case TypeCode.Double: - value = XmlConvert.ToString((double)o); + tryFormatResult = XmlConvert.TryFormat((double)o, _primitivesBuffer, out charsWritten); type = "double"; break; case TypeCode.Decimal: - value = XmlConvert.ToString((decimal)o); + tryFormatResult = XmlConvert.TryFormat((decimal)o, _primitivesBuffer, out charsWritten); type = "decimal"; break; case TypeCode.DateTime: - value = FromDateTime((DateTime)o); + tryFormatResult = TryFormatDateTime((DateTime)o, _primitivesBuffer, out charsWritten); type = "dateTime"; break; case TypeCode.Char: - value = FromChar((char)o); + tryFormatResult = XmlConvert.TryFormat((ushort)o, _primitivesBuffer, out charsWritten); type = "char"; typeNs = UrtTypes.Namespace; break; case TypeCode.Byte: - value = XmlConvert.ToString((byte)o); + tryFormatResult = XmlConvert.TryFormat((byte)o, _primitivesBuffer, out charsWritten); type = "unsignedByte"; break; case TypeCode.SByte: - value = XmlConvert.ToString((sbyte)o); + tryFormatResult = XmlConvert.TryFormat((sbyte)o, _primitivesBuffer, out charsWritten); type = "byte"; break; case TypeCode.UInt16: - value = XmlConvert.ToString((ushort)o); + tryFormatResult = XmlConvert.TryFormat((ushort)o, _primitivesBuffer, out charsWritten); type = "unsignedShort"; break; case TypeCode.UInt32: - value = XmlConvert.ToString((uint)o); + tryFormatResult = XmlConvert.TryFormat((uint)o, _primitivesBuffer, out charsWritten); type = "unsignedInt"; break; case TypeCode.UInt64: - value = XmlConvert.ToString((ulong)o); + tryFormatResult = XmlConvert.TryFormat((ulong)o, _primitivesBuffer, out charsWritten); type = "unsignedLong"; break; @@ -340,19 +350,19 @@ protected void WriteTypedPrimitive(string? name, string? ns, object o, bool xsiT } else if (t == typeof(Guid)) { - value = XmlConvert.ToString((Guid)o); + tryFormatResult = XmlConvert.TryFormat((Guid)o, _primitivesBuffer, out charsWritten); type = "guid"; typeNs = UrtTypes.Namespace; } else if (t == typeof(TimeSpan)) { - value = XmlConvert.ToString((TimeSpan)o); + tryFormatResult = XmlConvert.TryFormat((TimeSpan)o, _primitivesBuffer, out charsWritten); type = "TimeSpan"; typeNs = UrtTypes.Namespace; } else if (t == typeof(DateTimeOffset)) { - value = XmlConvert.ToString((DateTimeOffset)o); + tryFormatResult = XmlConvert.TryFormat((DateTimeOffset)o, _primitivesBuffer, out charsWritten); type = "dateTimeOffset"; typeNs = UrtTypes.Namespace; } @@ -387,21 +397,28 @@ protected void WriteTypedPrimitive(string? name, string? ns, object o, bool xsiT if (xsiType) WriteXsiType(type, typeNs); - if (value == null) - { - _w.WriteAttributeString("nil", XmlSchema.InstanceNamespace, "true"); - } - else if (writeDirect) + if (writeDirect) { // only one type currently writes directly to XML stream XmlCustomFormatter.WriteArrayBase64(_w, (byte[])o, 0, ((byte[])o).Length); } - else if (writeRaw) + else if (tryFormatResult != null) { - _w.WriteRaw(value); + //all the primitive types except string and XmlQualifiedName writes to the buffer + _w.WriteChars(_primitivesBuffer, 0, charsWritten); } else - _w.WriteString(value); + { + if (value == null) + _w.WriteAttributeString("nil", XmlSchema.InstanceNamespace, "true"); + else if (writeRaw) + { + _w.WriteRaw(value); + } + else + _w.WriteString(value); + } + _w.WriteEndElement(); } diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Serialization/Xmlcustomformatter.cs b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/Xmlcustomformatter.cs index 2338d8d46e949..77a2bb1863992 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Serialization/Xmlcustomformatter.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/Xmlcustomformatter.cs @@ -105,6 +105,17 @@ internal static string FromDateTime(DateTime value) } } + internal static bool TryFormatDateTime(DateTime value, Span destination, out int charsWritten) + { + if (Mode == DateTimeSerializationSection.DateTimeSerializationMode.Local) + { + return XmlConvert.TryFormat(value, "yyyy-MM-ddTHH:mm:ss.fffffffzzzzzz", destination, out charsWritten); + } + + // for mode DateTimeSerializationMode.Roundtrip and DateTimeSerializationMode.Default + return XmlConvert.TryFormat(value, XmlDateTimeSerializationMode.RoundtripKind, destination, out charsWritten); + } + internal static string FromChar(char value) { return XmlConvert.ToString((ushort)value); diff --git a/src/libraries/System.Private.Xml/src/System/Xml/XmlConvert.cs b/src/libraries/System.Private.Xml/src/System/Xml/XmlConvert.cs index b2c4e1ca9d005..2152fb76f204d 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/XmlConvert.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/XmlConvert.cs @@ -1849,6 +1849,11 @@ internal static bool TryFormat(TimeSpan value, Span destination, out int c return new XsdDuration(value).TryFormat(destination, out charsWritten); } + internal static bool TryFormat(DateTime value, [StringSyntax(StringSyntaxAttribute.DateTimeFormat)] string format, Span destination, out int charsWritten) + { + return value.TryFormat(destination, out charsWritten, format, DateTimeFormatInfo.InvariantInfo); + } + internal static bool TryFormat(DateTime value, Span destination, out int charsWritten) { return TryFormat(value, XmlDateTimeSerializationMode.RoundtripKind, destination, out charsWritten); From dcc61809ea69657502ca8d8f883a5898a165d953 Mon Sep 17 00:00:00 2001 From: Trayan Zapryanov Date: Fri, 30 Sep 2022 10:42:50 +0300 Subject: [PATCH 03/17] Fix char cast --- .../src/System/Xml/Serialization/XmlSerializationWriter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriter.cs b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriter.cs index 2e98410fb28b7..c442d996f4a2c 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriter.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriter.cs @@ -304,7 +304,7 @@ protected void WriteTypedPrimitive(string? name, string? ns, object o, bool xsiT type = "dateTime"; break; case TypeCode.Char: - tryFormatResult = XmlConvert.TryFormat((ushort)o, _primitivesBuffer, out charsWritten); + tryFormatResult = XmlConvert.TryFormat((ushort)(char)o, _primitivesBuffer, out charsWritten); type = "char"; typeNs = UrtTypes.Namespace; break; From f0558e46255b25bbc2340fd8467932ddc7fd65f6 Mon Sep 17 00:00:00 2001 From: Trayan Zapryanov Date: Fri, 30 Sep 2022 12:21:49 +0300 Subject: [PATCH 04/17] Add tests for different types --- .../XmlSerializerTests.RuntimeOnly.cs | 41 ++++++++++++++++--- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/src/libraries/System.Private.Xml/tests/XmlSerializer/XmlSerializerTests.RuntimeOnly.cs b/src/libraries/System.Private.Xml/tests/XmlSerializer/XmlSerializerTests.RuntimeOnly.cs index 29a7880968a5f..f7894093d3271 100644 --- a/src/libraries/System.Private.Xml/tests/XmlSerializer/XmlSerializerTests.RuntimeOnly.cs +++ b/src/libraries/System.Private.Xml/tests/XmlSerializer/XmlSerializerTests.RuntimeOnly.cs @@ -397,18 +397,49 @@ public static void Xml_EnumerableGenericRoot() [Fact] public static void Xml_CollectionRoot() { - MyCollection x = new MyCollection('a', 45); + DateTime now = new DateTime(2022, 9, 30, 9, 4, 15, DateTimeKind.Utc); + DateTimeOffset dtoNow = now.AddDays(1); + TimeSpan ts = new TimeSpan(1, 2, 3, 4, 5); + MyCollection x = new MyCollection('a', 45, + 123.45m, now, ts, dtoNow, (short)55, 2345324L, (sbyte)11, (ushort)34, (uint)4564, (ulong)456734767, + new byte[]{ 33, 44, 55}); MyCollection y = SerializeAndDeserialize(x, -@" +@" - 97 - 45 + 97 + 45 + 123.45 + 2022-09-30T09:04:15Z + P1DT2H3M4.005S + 2022-10-01T09:04:15Z + 55 + 2345324 + 11 + 34 + 4564 + 456734767 + ISw3 "); Assert.NotNull(y); - Assert.True(y.Count == 2); + Assert.True(y.Count == 13); Assert.True((char)y[0] == 'a'); Assert.True((int)y[1] == 45); + Assert.True((decimal)y[2] == 123.45m); + Assert.True((DateTime)y[3] == now); + Assert.True((TimeSpan)y[4] == ts); + Assert.True((DateTimeOffset)y[5] == dtoNow); + Assert.True((short)y[6] == 55); + Assert.True((long)y[7] == 2345324L); + Assert.True((sbyte)y[8] == 11); + Assert.True((ushort)y[9] == 34); + Assert.True((uint)y[10] == 4564); + Assert.True((ulong)y[11] == 456734767); + Assert.True(y[12] is byte[]); + Assert.Equal(3, ((byte[])y[12]).Length); + Assert.Equal(33, ((byte[])y[12])[0]); + Assert.Equal(44, ((byte[])y[12])[1]); + Assert.Equal(55, ((byte[])y[12])[2]); } [Fact] From 0f1db0d5897fedc0a6ef71dc4574ce2215b7ae56 Mon Sep 17 00:00:00 2001 From: Trayan Zapryanov Date: Fri, 30 Sep 2022 12:40:55 +0300 Subject: [PATCH 05/17] Add byte type --- .../tests/XmlSerializer/XmlSerializerTests.RuntimeOnly.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Private.Xml/tests/XmlSerializer/XmlSerializerTests.RuntimeOnly.cs b/src/libraries/System.Private.Xml/tests/XmlSerializer/XmlSerializerTests.RuntimeOnly.cs index f7894093d3271..b4f12039d3dc8 100644 --- a/src/libraries/System.Private.Xml/tests/XmlSerializer/XmlSerializerTests.RuntimeOnly.cs +++ b/src/libraries/System.Private.Xml/tests/XmlSerializer/XmlSerializerTests.RuntimeOnly.cs @@ -402,7 +402,7 @@ public static void Xml_CollectionRoot() TimeSpan ts = new TimeSpan(1, 2, 3, 4, 5); MyCollection x = new MyCollection('a', 45, 123.45m, now, ts, dtoNow, (short)55, 2345324L, (sbyte)11, (ushort)34, (uint)4564, (ulong)456734767, - new byte[]{ 33, 44, 55}); + new byte[]{ 33, 44, 55}, (byte)67); MyCollection y = SerializeAndDeserialize(x, @" @@ -419,10 +419,11 @@ public static void Xml_CollectionRoot() 4564 456734767 ISw3 + 67 "); Assert.NotNull(y); - Assert.True(y.Count == 13); + Assert.True(y.Count == 14); Assert.True((char)y[0] == 'a'); Assert.True((int)y[1] == 45); Assert.True((decimal)y[2] == 123.45m); @@ -440,6 +441,7 @@ public static void Xml_CollectionRoot() Assert.Equal(33, ((byte[])y[12])[0]); Assert.Equal(44, ((byte[])y[12])[1]); Assert.Equal(55, ((byte[])y[12])[2]); + Assert.True((byte)y[13] == 67); } [Fact] From 99b2169592d5476e5e3961b5ed2351808a6cef10 Mon Sep 17 00:00:00 2001 From: Trayan Zapryanov Date: Mon, 24 Oct 2022 15:48:53 +0300 Subject: [PATCH 06/17] Address feedback --- .../src/System/Xml/Schema/XsdDuration.cs | 26 ++++---- .../XmlSerializerTests.RuntimeOnly.cs | 62 +++++++++++-------- 2 files changed, 49 insertions(+), 39 deletions(-) diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDuration.cs b/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDuration.cs index a32f639b2c4c9..87315b71a1c63 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDuration.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDuration.cs @@ -335,28 +335,22 @@ public override string ToString() return ToString(DurationType.Duration); } - public bool TryFormat(Span destination, out int charsWritten, DurationType durationType = DurationType.Duration) - { - var sb = new ValueStringBuilder(stackalloc char[CharStackBufferSize]); - Format(ref sb, durationType); - - return sb.TryCopyTo(destination, out charsWritten); - } - /// /// Return the string representation according to xsd:duration rules, xdt:dayTimeDuration rules, or /// xdt:yearMonthDuration rules. /// internal string ToString(DurationType durationType) { - var vsb = new ValueStringBuilder(stackalloc char[CharStackBufferSize]); - Format(ref vsb, durationType); + Span destination = stackalloc char[CharStackBufferSize]; + bool success = TryFormat(destination, out int _, durationType); + Debug.Assert(success); - return vsb.ToString(); + return destination.ToString(); } - private void Format(ref ValueStringBuilder vsb, DurationType durationType) + public bool TryFormat(Span destination, out int charsWritten, DurationType durationType = DurationType.Duration) { + var vsb = new ValueStringBuilder(destination); int nanoseconds, digit, zeroIdx, len; if (IsNegative) @@ -426,8 +420,9 @@ private void Format(ref ValueStringBuilder vsb, DurationType durationType) } vsb.EnsureCapacity(zeroIdx + 1); - var nanoSpanLength = zeroIdx - len + 1; - tmpSpan[..nanoSpanLength].TryCopyTo(vsb.AppendSpan(nanoSpanLength)); + int nanoSpanLength = zeroIdx - len + 1; + bool successCopy = tmpSpan[..nanoSpanLength].TryCopyTo(vsb.AppendSpan(nanoSpanLength)); + Debug.Assert(successCopy); } vsb.Append('S'); } @@ -443,6 +438,9 @@ private void Format(ref ValueStringBuilder vsb, DurationType durationType) if (vsb[vsb.Length - 1] == 'P') vsb.Append("0M"); } + + charsWritten = vsb.Length; + return destination.Length >= vsb.Length; } internal static Exception? TryParse(string s, out XsdDuration result) diff --git a/src/libraries/System.Private.Xml/tests/XmlSerializer/XmlSerializerTests.RuntimeOnly.cs b/src/libraries/System.Private.Xml/tests/XmlSerializer/XmlSerializerTests.RuntimeOnly.cs index b4f12039d3dc8..204f52d5e4287 100644 --- a/src/libraries/System.Private.Xml/tests/XmlSerializer/XmlSerializerTests.RuntimeOnly.cs +++ b/src/libraries/System.Private.Xml/tests/XmlSerializer/XmlSerializerTests.RuntimeOnly.cs @@ -396,18 +396,32 @@ public static void Xml_EnumerableGenericRoot() [Fact] public static void Xml_CollectionRoot() + { + MyCollection x = new MyCollection('a', 45); + MyCollection y = SerializeAndDeserialize(x, +@" + + 97 + 45 +"); + + Assert.NotNull(y); + Assert.True(y.Count == 2); + Assert.True((char)y[0] == 'a'); + Assert.True((int)y[1] == 45); + } + + [Fact] + public static void Xml_CollectionRoot_MorePrimitiveTypes() { DateTime now = new DateTime(2022, 9, 30, 9, 4, 15, DateTimeKind.Utc); DateTimeOffset dtoNow = now.AddDays(1); TimeSpan ts = new TimeSpan(1, 2, 3, 4, 5); - MyCollection x = new MyCollection('a', 45, - 123.45m, now, ts, dtoNow, (short)55, 2345324L, (sbyte)11, (ushort)34, (uint)4564, (ulong)456734767, - new byte[]{ 33, 44, 55}, (byte)67); + MyCollection x = new MyCollection(123.45m, now, ts, dtoNow, (short)55, 2345324L, (sbyte)11, (ushort)34, (uint)4564, (ulong)456734767, + new byte[] { 33, 44, 55 }, (byte)67); MyCollection y = SerializeAndDeserialize(x, -@" +@" - 97 - 45 123.45 2022-09-30T09:04:15Z P1DT2H3M4.005S @@ -423,25 +437,23 @@ public static void Xml_CollectionRoot() "); Assert.NotNull(y); - Assert.True(y.Count == 14); - Assert.True((char)y[0] == 'a'); - Assert.True((int)y[1] == 45); - Assert.True((decimal)y[2] == 123.45m); - Assert.True((DateTime)y[3] == now); - Assert.True((TimeSpan)y[4] == ts); - Assert.True((DateTimeOffset)y[5] == dtoNow); - Assert.True((short)y[6] == 55); - Assert.True((long)y[7] == 2345324L); - Assert.True((sbyte)y[8] == 11); - Assert.True((ushort)y[9] == 34); - Assert.True((uint)y[10] == 4564); - Assert.True((ulong)y[11] == 456734767); - Assert.True(y[12] is byte[]); - Assert.Equal(3, ((byte[])y[12]).Length); - Assert.Equal(33, ((byte[])y[12])[0]); - Assert.Equal(44, ((byte[])y[12])[1]); - Assert.Equal(55, ((byte[])y[12])[2]); - Assert.True((byte)y[13] == 67); + Assert.True(y.Count == 12); + Assert.True((decimal)y[0] == 123.45m); + Assert.True((DateTime)y[1] == now); + Assert.True((TimeSpan)y[2] == ts); + Assert.True((DateTimeOffset)y[3] == dtoNow); + Assert.True((short)y[4] == 55); + Assert.True((long)y[5] == 2345324L); + Assert.True((sbyte)y[6] == 11); + Assert.True((ushort)y[7] == 34); + Assert.True((uint)y[8] == 4564); + Assert.True((ulong)y[9] == 456734767); + Assert.True(y[10] is byte[]); + Assert.Equal(3, ((byte[])y[10]).Length); + Assert.Equal(33, ((byte[])y[10])[0]); + Assert.Equal(44, ((byte[])y[10])[1]); + Assert.Equal(55, ((byte[])y[10])[2]); + Assert.True((byte)y[11] == 67); } [Fact] From 6d5f5f6e041cfab6739b0cc5879f735caa2a9c81 Mon Sep 17 00:00:00 2001 From: Trayan Zapryanov Date: Tue, 25 Oct 2022 08:45:40 +0300 Subject: [PATCH 07/17] Fix tests --- .../src/System/Xml/Schema/XsdDateTime.cs | 19 +++++++++---------- .../src/System/Xml/Schema/XsdDuration.cs | 4 ++-- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDateTime.cs b/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDateTime.cs index 95c0f8d94aa5b..b4e01132f2631 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDateTime.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDateTime.cs @@ -5,6 +5,7 @@ using System.Xml; using System.Diagnostics; using System.Text; +using static System.Xml.Schema.XsdDuration; namespace System.Xml.Schema { @@ -496,22 +497,17 @@ public static implicit operator DateTimeOffset(XsdDateTime xdt) /// public override string ToString() { - var vsb = new ValueStringBuilder(stackalloc char[CharStackBufferSize]); - Format(ref vsb); + Span destination = stackalloc char[CharStackBufferSize]; + bool success = TryFormat(destination, out int charsWritten); + Debug.Assert(success); - return vsb.ToString(); + return destination.Slice(0, charsWritten).ToString(); } public bool TryFormat(Span destination, out int charsWritten) { - var sb = new ValueStringBuilder(stackalloc char[CharStackBufferSize]); - Format(ref sb); + var vsb = new ValueStringBuilder(destination); - return sb.TryCopyTo(destination, out charsWritten); - } - - private void Format(ref ValueStringBuilder vsb) - { switch (InternalTypeCode) { case DateTimeTypeCode.DateTime: @@ -550,6 +546,9 @@ private void Format(ref ValueStringBuilder vsb) break; } PrintZone(ref vsb); + + charsWritten = vsb.Length; + return destination.Length >= vsb.Length; } // Serialize year, month and day diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDuration.cs b/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDuration.cs index 87315b71a1c63..6bb75b24e2fdd 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDuration.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDuration.cs @@ -342,10 +342,10 @@ public override string ToString() internal string ToString(DurationType durationType) { Span destination = stackalloc char[CharStackBufferSize]; - bool success = TryFormat(destination, out int _, durationType); + bool success = TryFormat(destination, out int charsWritten, durationType); Debug.Assert(success); - return destination.ToString(); + return destination.Slice(0, charsWritten).ToString(); } public bool TryFormat(Span destination, out int charsWritten, DurationType durationType = DurationType.Duration) From a51e952b357f6f561f1ed63de2468380fb18856d Mon Sep 17 00:00:00 2001 From: Trayan Zapryanov Date: Tue, 25 Oct 2022 08:49:52 +0300 Subject: [PATCH 08/17] remove using --- .../System.Private.Xml/src/System/Xml/Schema/XsdDateTime.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDateTime.cs b/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDateTime.cs index b4e01132f2631..814bcfb1f3932 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDateTime.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDateTime.cs @@ -5,7 +5,6 @@ using System.Xml; using System.Diagnostics; using System.Text; -using static System.Xml.Schema.XsdDuration; namespace System.Xml.Schema { From b7e9b9d6092e3d45583515246f79085a6f049b6b Mon Sep 17 00:00:00 2001 From: Trayan Zapryanov Date: Tue, 25 Oct 2022 10:29:09 +0300 Subject: [PATCH 09/17] Increase duration char buffer size as it is not enough for TimeSpan.Max/Min --- .../System.Private.Xml/src/System/Xml/Schema/XsdDuration.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDuration.cs b/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDuration.cs index 6bb75b24e2fdd..135d8a5783e09 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDuration.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDuration.cs @@ -23,7 +23,7 @@ internal struct XsdDuration private uint _nanoseconds; // High bit is used to indicate whether duration is negative private const uint NegativeBit = 0x80000000; - private const int CharStackBufferSize = 20; + private const int CharStackBufferSize = 32; private enum Parts { From 7a1a1dc55ee6702a08664e2380778c1eba1319d2 Mon Sep 17 00:00:00 2001 From: Trayan Zapryanov Date: Wed, 2 Nov 2022 20:06:42 +0200 Subject: [PATCH 10/17] Address feedback --- .../src/System/Xml/XmlConvert.cs | 126 ++++-------------- 1 file changed, 29 insertions(+), 97 deletions(-) diff --git a/src/libraries/System.Private.Xml/src/System/Xml/XmlConvert.cs b/src/libraries/System.Private.Xml/src/System/Xml/XmlConvert.cs index 2152fb76f204d..653fba3907b29 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/XmlConvert.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/XmlConvert.cs @@ -1667,36 +1667,10 @@ internal static Exception CreateInvalidNameCharException(string name, int index, internal static bool TryFormat(bool value, Span destination, out int charsWritten) { - if (value) - { - if (destination.Length < 4) - { - charsWritten = -1; - return false; - } - - destination[0] = 't'; - destination[1] = 'r'; - destination[2] = 'u'; - destination[3] = 'e'; - charsWritten = 4; - - return true; - } - - if (destination.Length < 5) - { - charsWritten = -1; - return false; - } + ReadOnlySpan valueSpan = value ? "true" : "false"; - destination[0] = 'f'; - destination[1] = 'a'; - destination[2] = 'l'; - destination[3] = 's'; - destination[4] = 'e'; - charsWritten = 5; - return true; + charsWritten = valueSpan.Length; + return valueSpan.TryCopyTo(destination); } internal static bool TryFormat(char value, Span destination, out int charsWritten) @@ -1756,92 +1730,50 @@ internal static bool TryFormat(ulong value, Span destination, out int char internal static bool TryFormat(float value, Span destination, out int charsWritten) { + ReadOnlySpan valueSpan = default; + if (float.IsNegativeInfinity(value)) { - if (destination.Length < 4) - { - charsWritten = -1; - return false; - } - destination[0] = '-'; - destination[1] = 'I'; - destination[2] = 'N'; - destination[3] = 'F'; - charsWritten = 4; - return true; + valueSpan = "-INF"; } - - if (float.IsPositiveInfinity(value)) + else if (float.IsPositiveInfinity(value)) { - if (destination.Length < 3) - { - charsWritten = -1; - return false; - } - destination[0] = 'I'; - destination[1] = 'N'; - destination[2] = 'F'; - charsWritten = 3; - return true; + valueSpan = "INF"; } - if (IsNegativeZero((double)value)) + else if (IsNegativeZero((double)value)) { - if (destination.Length < 2) - { - charsWritten = -1; - return false; - } - destination[0] = '-'; - destination[1] = '0'; - charsWritten = 2; - return true; + valueSpan = "-0"; } - return value.TryFormat(destination, out charsWritten, "R", NumberFormatInfo.InvariantInfo); + + if (valueSpan.IsEmpty) + return value.TryFormat(destination, out charsWritten, "R", NumberFormatInfo.InvariantInfo); + + charsWritten = valueSpan.Length; + return valueSpan.TryCopyTo(destination); } internal static bool TryFormat(double value, Span destination, out int charsWritten) { + ReadOnlySpan valueSpan = default; + if (double.IsNegativeInfinity(value)) { - if (destination.Length < 4) - { - charsWritten = -1; - return false; - } - destination[0] = '-'; - destination[1] = 'I'; - destination[2] = 'N'; - destination[3] = 'F'; - charsWritten = 4; - return true; + valueSpan = "-INF"; } - - if (double.IsPositiveInfinity(value)) + else if (double.IsPositiveInfinity(value)) { - if (destination.Length < 3) - { - charsWritten = -1; - return false; - } - destination[0] = 'I'; - destination[1] = 'N'; - destination[2] = 'F'; - charsWritten = 3; - return true; + valueSpan = "INF"; } - if (IsNegativeZero(value)) + else if (IsNegativeZero(value)) { - if (destination.Length < 2) - { - charsWritten = -1; - return false; - } - destination[0] = '-'; - destination[1] = '0'; - charsWritten = 2; - return true; + valueSpan = "-0"; } - return value.TryFormat(destination, out charsWritten, "R", NumberFormatInfo.InvariantInfo); + + if (valueSpan.IsEmpty) + return value.TryFormat(destination, out charsWritten, "R", NumberFormatInfo.InvariantInfo); + + charsWritten = valueSpan.Length; + return valueSpan.TryCopyTo(destination); } internal static bool TryFormat(TimeSpan value, Span destination, out int charsWritten) From ed66817906228d6754b4c1b580eda0840f48830a Mon Sep 17 00:00:00 2001 From: Traian Zaprianov Date: Wed, 23 Nov 2022 22:14:17 +0200 Subject: [PATCH 11/17] Added assert if we cannot format primitive value to the suppiled buffer --- .../src/System/Xml/Serialization/XmlSerializationWriter.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriter.cs b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriter.cs index c442d996f4a2c..1462009e44fe3 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriter.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriter.cs @@ -404,6 +404,7 @@ protected void WriteTypedPrimitive(string? name, string? ns, object o, bool xsiT } else if (tryFormatResult != null) { + Debug.Assert(tryFormatResult.Value, "Something goes wrong with formatting primitives to the buffer."); //all the primitive types except string and XmlQualifiedName writes to the buffer _w.WriteChars(_primitivesBuffer, 0, charsWritten); } From f3ca604e80e260a4382176828324f2287d1d1f75 Mon Sep 17 00:00:00 2001 From: Traian Zaprianov Date: Thu, 24 Nov 2022 18:04:36 +0200 Subject: [PATCH 12/17] Lazy create primitives buffer --- .../Serialization/XmlSerializationWriter.cs | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriter.cs b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriter.cs index 773269e9a7b44..30973d3acceae 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriter.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriter.cs @@ -39,7 +39,7 @@ public abstract class XmlSerializationWriter : XmlSerializationGeneratedCode private bool _escapeName = true; //char buffer for serializing primitive values - private readonly char[] _primitivesBuffer = new char[128]; + private char[]? _primitivesBuffer; // this method must be called before any generated serialization methods are called internal void Init(XmlWriter w, XmlSerializerNamespaces? namespaces, string? encodingStyle, string? idBase) @@ -264,6 +264,7 @@ protected void WriteTypedPrimitive(string? name, string? ns, object o, bool xsiT bool? tryFormatResult = null; int charsWritten = -1; + char[] buffer = _primitivesBuffer ??= new char[128]; switch (Type.GetTypeCode(t)) { case TypeCode.String: @@ -272,60 +273,60 @@ protected void WriteTypedPrimitive(string? name, string? ns, object o, bool xsiT writeRaw = false; break; case TypeCode.Int32: - tryFormatResult = XmlConvert.TryFormat((int)o, _primitivesBuffer, out charsWritten); + tryFormatResult = XmlConvert.TryFormat((int)o, buffer, out charsWritten); type = "int"; break; case TypeCode.Boolean: - tryFormatResult = XmlConvert.TryFormat((bool)o, _primitivesBuffer, out charsWritten); + tryFormatResult = XmlConvert.TryFormat((bool)o, buffer, out charsWritten); type = "boolean"; break; case TypeCode.Int16: - tryFormatResult = XmlConvert.TryFormat((short)o, _primitivesBuffer, out charsWritten); + tryFormatResult = XmlConvert.TryFormat((short)o, buffer, out charsWritten); type = "short"; break; case TypeCode.Int64: - tryFormatResult = XmlConvert.TryFormat((long)o, _primitivesBuffer, out charsWritten); + tryFormatResult = XmlConvert.TryFormat((long)o, buffer, out charsWritten); type = "long"; break; case TypeCode.Single: - tryFormatResult = XmlConvert.TryFormat((float)o, _primitivesBuffer, out charsWritten); + tryFormatResult = XmlConvert.TryFormat((float)o, buffer, out charsWritten); type = "float"; break; case TypeCode.Double: - tryFormatResult = XmlConvert.TryFormat((double)o, _primitivesBuffer, out charsWritten); + tryFormatResult = XmlConvert.TryFormat((double)o, buffer, out charsWritten); type = "double"; break; case TypeCode.Decimal: - tryFormatResult = XmlConvert.TryFormat((decimal)o, _primitivesBuffer, out charsWritten); + tryFormatResult = XmlConvert.TryFormat((decimal)o, buffer, out charsWritten); type = "decimal"; break; case TypeCode.DateTime: - tryFormatResult = TryFormatDateTime((DateTime)o, _primitivesBuffer, out charsWritten); + tryFormatResult = TryFormatDateTime((DateTime)o, buffer, out charsWritten); type = "dateTime"; break; case TypeCode.Char: - tryFormatResult = XmlConvert.TryFormat((ushort)(char)o, _primitivesBuffer, out charsWritten); + tryFormatResult = XmlConvert.TryFormat((ushort)(char)o, buffer, out charsWritten); type = "char"; typeNs = UrtTypes.Namespace; break; case TypeCode.Byte: - tryFormatResult = XmlConvert.TryFormat((byte)o, _primitivesBuffer, out charsWritten); + tryFormatResult = XmlConvert.TryFormat((byte)o, buffer, out charsWritten); type = "unsignedByte"; break; case TypeCode.SByte: - tryFormatResult = XmlConvert.TryFormat((sbyte)o, _primitivesBuffer, out charsWritten); + tryFormatResult = XmlConvert.TryFormat((sbyte)o, buffer, out charsWritten); type = "byte"; break; case TypeCode.UInt16: - tryFormatResult = XmlConvert.TryFormat((ushort)o, _primitivesBuffer, out charsWritten); + tryFormatResult = XmlConvert.TryFormat((ushort)o, buffer, out charsWritten); type = "unsignedShort"; break; case TypeCode.UInt32: - tryFormatResult = XmlConvert.TryFormat((uint)o, _primitivesBuffer, out charsWritten); + tryFormatResult = XmlConvert.TryFormat((uint)o, buffer, out charsWritten); type = "unsignedInt"; break; case TypeCode.UInt64: - tryFormatResult = XmlConvert.TryFormat((ulong)o, _primitivesBuffer, out charsWritten); + tryFormatResult = XmlConvert.TryFormat((ulong)o, buffer, out charsWritten); type = "unsignedLong"; break; @@ -350,19 +351,19 @@ protected void WriteTypedPrimitive(string? name, string? ns, object o, bool xsiT } else if (t == typeof(Guid)) { - tryFormatResult = XmlConvert.TryFormat((Guid)o, _primitivesBuffer, out charsWritten); + tryFormatResult = XmlConvert.TryFormat((Guid)o, buffer, out charsWritten); type = "guid"; typeNs = UrtTypes.Namespace; } else if (t == typeof(TimeSpan)) { - tryFormatResult = XmlConvert.TryFormat((TimeSpan)o, _primitivesBuffer, out charsWritten); + tryFormatResult = XmlConvert.TryFormat((TimeSpan)o, buffer, out charsWritten); type = "TimeSpan"; typeNs = UrtTypes.Namespace; } else if (t == typeof(DateTimeOffset)) { - tryFormatResult = XmlConvert.TryFormat((DateTimeOffset)o, _primitivesBuffer, out charsWritten); + tryFormatResult = XmlConvert.TryFormat((DateTimeOffset)o, buffer, out charsWritten); type = "dateTimeOffset"; typeNs = UrtTypes.Namespace; } @@ -406,7 +407,7 @@ protected void WriteTypedPrimitive(string? name, string? ns, object o, bool xsiT { Debug.Assert(tryFormatResult.Value, "Something goes wrong with formatting primitives to the buffer."); //all the primitive types except string and XmlQualifiedName writes to the buffer - _w.WriteChars(_primitivesBuffer, 0, charsWritten); + _w.WriteChars(buffer, 0, charsWritten); } else { From 6a3b7849ca09a175d198039816aeee76e53a5513 Mon Sep 17 00:00:00 2001 From: Traian Zaprianov Date: Sun, 22 Jan 2023 12:13:47 +0200 Subject: [PATCH 13/17] Address new feadback --- .../System.Private.Xml/src/System/Xml/XmlConvert.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/libraries/System.Private.Xml/src/System/Xml/XmlConvert.cs b/src/libraries/System.Private.Xml/src/System/Xml/XmlConvert.cs index d50d6e0c47bb7..6362557349dc3 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/XmlConvert.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/XmlConvert.cs @@ -1648,19 +1648,18 @@ internal static Exception CreateInvalidNameCharException(string name, int index, internal static bool TryFormat(bool value, Span destination, out int charsWritten) { - ReadOnlySpan valueSpan = value ? "true" : "false"; + string valueAsString = value ? "true" : "false"; - charsWritten = valueSpan.Length; - return valueSpan.TryCopyTo(destination); + charsWritten = valueAsString.Length; + return valueAsString.TryCopyTo(destination); } internal static bool TryFormat(char value, Span destination, out int charsWritten) { - charsWritten = -1; + charsWritten = 1; if (destination.Length < 1) return false; destination[0] = value; - charsWritten = 1; return true; } From f4f5a9eb411883c1b8fc851b7ac528a1727be392 Mon Sep 17 00:00:00 2001 From: Traian Zaprianov Date: Tue, 24 Jan 2023 21:32:46 +0200 Subject: [PATCH 14/17] Resolve feedback --- .../Serialization/XmlSerializationWriter.cs | 19 ++++++++++++++----- .../src/System/Xml/XmlConvert.cs | 14 ++++++++------ .../XmlSerializerTests.RuntimeOnly.cs | 4 ++-- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriter.cs b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriter.cs index 30973d3acceae..069919d46eae5 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriter.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriter.cs @@ -18,6 +18,7 @@ using System.Xml; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; +using System.Buffers; namespace System.Xml.Serialization { @@ -38,9 +39,6 @@ public abstract class XmlSerializationWriter : XmlSerializationGeneratedCode private bool _soap12; private bool _escapeName = true; - //char buffer for serializing primitive values - private char[]? _primitivesBuffer; - // this method must be called before any generated serialization methods are called internal void Init(XmlWriter w, XmlSerializerNamespaces? namespaces, string? encodingStyle, string? idBase) { @@ -264,7 +262,7 @@ protected void WriteTypedPrimitive(string? name, string? ns, object o, bool xsiT bool? tryFormatResult = null; int charsWritten = -1; - char[] buffer = _primitivesBuffer ??= new char[128]; + char[] buffer = ArrayPool.Shared.Rent(128); switch (Type.GetTypeCode(t)) { case TypeCode.String: @@ -382,10 +380,15 @@ protected void WriteTypedPrimitive(string? name, string? ns, object o, bool xsiT xmlNodes[i].WriteTo(_w); } _w.WriteEndElement(); + ArrayPool.Shared.Return(buffer); return; } else + { + ArrayPool.Shared.Return(buffer); throw CreateUnknownTypeException(t); + } + break; } if (!wroteStartElement) @@ -406,8 +409,13 @@ protected void WriteTypedPrimitive(string? name, string? ns, object o, bool xsiT else if (tryFormatResult != null) { Debug.Assert(tryFormatResult.Value, "Something goes wrong with formatting primitives to the buffer."); +#if DEBUG + const string escapeChars = "<>\"'&"; + ReadOnlySpan span = buffer; + Debug.Assert(span.IndexOfAny(escapeChars) == 0, "Primitive value contains illegal xml char."); +#endif //all the primitive types except string and XmlQualifiedName writes to the buffer - _w.WriteChars(buffer, 0, charsWritten); + _w.WriteRaw(buffer, 0, charsWritten); } else { @@ -422,6 +430,7 @@ protected void WriteTypedPrimitive(string? name, string? ns, object o, bool xsiT } _w.WriteEndElement(); + ArrayPool.Shared.Return(buffer); } private string GetQualifiedName(string name, string? ns) diff --git a/src/libraries/System.Private.Xml/src/System/Xml/XmlConvert.cs b/src/libraries/System.Private.Xml/src/System/Xml/XmlConvert.cs index 700045808af5b..9cc448a553dc8 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/XmlConvert.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/XmlConvert.cs @@ -1710,7 +1710,7 @@ internal static bool TryFormat(ulong value, Span destination, out int char internal static bool TryFormat(float value, Span destination, out int charsWritten) { - ReadOnlySpan valueSpan = default; + ReadOnlySpan valueSpan; if (float.IsNegativeInfinity(value)) { @@ -1724,9 +1724,10 @@ internal static bool TryFormat(float value, Span destination, out int char { valueSpan = "-0"; } - - if (valueSpan.IsEmpty) + else + { return value.TryFormat(destination, out charsWritten, "R", NumberFormatInfo.InvariantInfo); + } charsWritten = valueSpan.Length; return valueSpan.TryCopyTo(destination); @@ -1734,7 +1735,7 @@ internal static bool TryFormat(float value, Span destination, out int char internal static bool TryFormat(double value, Span destination, out int charsWritten) { - ReadOnlySpan valueSpan = default; + ReadOnlySpan valueSpan; if (double.IsNegativeInfinity(value)) { @@ -1748,9 +1749,10 @@ internal static bool TryFormat(double value, Span destination, out int cha { valueSpan = "-0"; } - - if (valueSpan.IsEmpty) + else + { return value.TryFormat(destination, out charsWritten, "R", NumberFormatInfo.InvariantInfo); + } charsWritten = valueSpan.Length; return valueSpan.TryCopyTo(destination); diff --git a/src/libraries/System.Private.Xml/tests/XmlSerializer/XmlSerializerTests.RuntimeOnly.cs b/src/libraries/System.Private.Xml/tests/XmlSerializer/XmlSerializerTests.RuntimeOnly.cs index 204f52d5e4287..9fa615c142d72 100644 --- a/src/libraries/System.Private.Xml/tests/XmlSerializer/XmlSerializerTests.RuntimeOnly.cs +++ b/src/libraries/System.Private.Xml/tests/XmlSerializer/XmlSerializerTests.RuntimeOnly.cs @@ -401,8 +401,8 @@ public static void Xml_CollectionRoot() MyCollection y = SerializeAndDeserialize(x, @" - 97 - 45 + 97 + 45 "); Assert.NotNull(y); From b8582e109f7d8dc346d4306c323a32acf072559e Mon Sep 17 00:00:00 2001 From: Traian Zaprianov Date: Wed, 25 Jan 2023 13:50:38 +0200 Subject: [PATCH 15/17] Optimize float and double TryFormat --- .../src/System/Xml/XmlConvert.cs | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/libraries/System.Private.Xml/src/System/Xml/XmlConvert.cs b/src/libraries/System.Private.Xml/src/System/Xml/XmlConvert.cs index 9cc448a553dc8..07b6bb93691a7 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/XmlConvert.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/XmlConvert.cs @@ -1712,13 +1712,12 @@ internal static bool TryFormat(float value, Span destination, out int char { ReadOnlySpan valueSpan; - if (float.IsNegativeInfinity(value)) + if (!float.IsFinite(value)) { - valueSpan = "-INF"; - } - else if (float.IsPositiveInfinity(value)) - { - valueSpan = "INF"; + if (float.IsNaN(value)) + valueSpan = "NaN"; + else + valueSpan = float.IsNegative(value) ? "-INF" : "INF"; } else if (IsNegativeZero((double)value)) { @@ -1737,13 +1736,12 @@ internal static bool TryFormat(double value, Span destination, out int cha { ReadOnlySpan valueSpan; - if (double.IsNegativeInfinity(value)) - { - valueSpan = "-INF"; - } - else if (double.IsPositiveInfinity(value)) + if (!double.IsFinite(value)) { - valueSpan = "INF"; + if (double.IsNaN(value)) + valueSpan = "NaN"; + else + valueSpan = double.IsNegative(value) ? "-INF" : "INF"; } else if (IsNegativeZero(value)) { From 12a51de8cbbebed2d4d86bceba686b0263361bb2 Mon Sep 17 00:00:00 2001 From: Traian Zaprianov Date: Sat, 4 Feb 2023 10:43:15 +0200 Subject: [PATCH 16/17] Replace ArrayPool renting with Interlocked. Fix Debug.Assert --- .../Xml/Serialization/XmlSerializationWriter.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriter.cs b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriter.cs index 069919d46eae5..650f82ad10e5c 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriter.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriter.cs @@ -18,7 +18,6 @@ using System.Xml; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; -using System.Buffers; namespace System.Xml.Serialization { @@ -39,6 +38,9 @@ public abstract class XmlSerializationWriter : XmlSerializationGeneratedCode private bool _soap12; private bool _escapeName = true; + //char buffer for serializing primitive values + private char[]? _primitivesBuffer; + // this method must be called before any generated serialization methods are called internal void Init(XmlWriter w, XmlSerializerNamespaces? namespaces, string? encodingStyle, string? idBase) { @@ -262,7 +264,7 @@ protected void WriteTypedPrimitive(string? name, string? ns, object o, bool xsiT bool? tryFormatResult = null; int charsWritten = -1; - char[] buffer = ArrayPool.Shared.Rent(128); + char[] buffer = Interlocked.Exchange(ref _primitivesBuffer, null) ?? new char[128]; switch (Type.GetTypeCode(t)) { case TypeCode.String: @@ -380,12 +382,12 @@ protected void WriteTypedPrimitive(string? name, string? ns, object o, bool xsiT xmlNodes[i].WriteTo(_w); } _w.WriteEndElement(); - ArrayPool.Shared.Return(buffer); + Interlocked.Exchange(ref _primitivesBuffer, buffer); return; } else { - ArrayPool.Shared.Return(buffer); + Interlocked.Exchange(ref _primitivesBuffer, buffer); throw CreateUnknownTypeException(t); } @@ -412,7 +414,7 @@ protected void WriteTypedPrimitive(string? name, string? ns, object o, bool xsiT #if DEBUG const string escapeChars = "<>\"'&"; ReadOnlySpan span = buffer; - Debug.Assert(span.IndexOfAny(escapeChars) == 0, "Primitive value contains illegal xml char."); + Debug.Assert(span.Slice(0, charsWritten).IndexOfAny(escapeChars) == -1, "Primitive value contains illegal xml char."); #endif //all the primitive types except string and XmlQualifiedName writes to the buffer _w.WriteRaw(buffer, 0, charsWritten); @@ -430,7 +432,7 @@ protected void WriteTypedPrimitive(string? name, string? ns, object o, bool xsiT } _w.WriteEndElement(); - ArrayPool.Shared.Return(buffer); + Interlocked.Exchange(ref _primitivesBuffer, buffer); } private string GetQualifiedName(string name, string? ns) From c5bf23bc3cc9cb46d31c4f10cb07eaf46398c387 Mon Sep 17 00:00:00 2001 From: Traian Zaprianov Date: Fri, 24 Mar 2023 13:53:15 +0200 Subject: [PATCH 17/17] Do not expect concurrency when using primitives buffer --- .../Serialization/XmlSerializationWriter.cs | 44 +++++++++---------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriter.cs b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriter.cs index 650f82ad10e5c..98ec06f03479a 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriter.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriter.cs @@ -39,7 +39,7 @@ public abstract class XmlSerializationWriter : XmlSerializationGeneratedCode private bool _escapeName = true; //char buffer for serializing primitive values - private char[]? _primitivesBuffer; + private readonly char[] _primitivesBuffer = new char[64]; // this method must be called before any generated serialization methods are called internal void Init(XmlWriter w, XmlSerializerNamespaces? namespaces, string? encodingStyle, string? idBase) @@ -264,7 +264,6 @@ protected void WriteTypedPrimitive(string? name, string? ns, object o, bool xsiT bool? tryFormatResult = null; int charsWritten = -1; - char[] buffer = Interlocked.Exchange(ref _primitivesBuffer, null) ?? new char[128]; switch (Type.GetTypeCode(t)) { case TypeCode.String: @@ -273,60 +272,60 @@ protected void WriteTypedPrimitive(string? name, string? ns, object o, bool xsiT writeRaw = false; break; case TypeCode.Int32: - tryFormatResult = XmlConvert.TryFormat((int)o, buffer, out charsWritten); + tryFormatResult = XmlConvert.TryFormat((int)o, _primitivesBuffer, out charsWritten); type = "int"; break; case TypeCode.Boolean: - tryFormatResult = XmlConvert.TryFormat((bool)o, buffer, out charsWritten); + tryFormatResult = XmlConvert.TryFormat((bool)o, _primitivesBuffer, out charsWritten); type = "boolean"; break; case TypeCode.Int16: - tryFormatResult = XmlConvert.TryFormat((short)o, buffer, out charsWritten); + tryFormatResult = XmlConvert.TryFormat((short)o, _primitivesBuffer, out charsWritten); type = "short"; break; case TypeCode.Int64: - tryFormatResult = XmlConvert.TryFormat((long)o, buffer, out charsWritten); + tryFormatResult = XmlConvert.TryFormat((long)o, _primitivesBuffer, out charsWritten); type = "long"; break; case TypeCode.Single: - tryFormatResult = XmlConvert.TryFormat((float)o, buffer, out charsWritten); + tryFormatResult = XmlConvert.TryFormat((float)o, _primitivesBuffer, out charsWritten); type = "float"; break; case TypeCode.Double: - tryFormatResult = XmlConvert.TryFormat((double)o, buffer, out charsWritten); + tryFormatResult = XmlConvert.TryFormat((double)o, _primitivesBuffer, out charsWritten); type = "double"; break; case TypeCode.Decimal: - tryFormatResult = XmlConvert.TryFormat((decimal)o, buffer, out charsWritten); + tryFormatResult = XmlConvert.TryFormat((decimal)o, _primitivesBuffer, out charsWritten); type = "decimal"; break; case TypeCode.DateTime: - tryFormatResult = TryFormatDateTime((DateTime)o, buffer, out charsWritten); + tryFormatResult = TryFormatDateTime((DateTime)o, _primitivesBuffer, out charsWritten); type = "dateTime"; break; case TypeCode.Char: - tryFormatResult = XmlConvert.TryFormat((ushort)(char)o, buffer, out charsWritten); + tryFormatResult = XmlConvert.TryFormat((ushort)(char)o, _primitivesBuffer, out charsWritten); type = "char"; typeNs = UrtTypes.Namespace; break; case TypeCode.Byte: - tryFormatResult = XmlConvert.TryFormat((byte)o, buffer, out charsWritten); + tryFormatResult = XmlConvert.TryFormat((byte)o, _primitivesBuffer, out charsWritten); type = "unsignedByte"; break; case TypeCode.SByte: - tryFormatResult = XmlConvert.TryFormat((sbyte)o, buffer, out charsWritten); + tryFormatResult = XmlConvert.TryFormat((sbyte)o, _primitivesBuffer, out charsWritten); type = "byte"; break; case TypeCode.UInt16: - tryFormatResult = XmlConvert.TryFormat((ushort)o, buffer, out charsWritten); + tryFormatResult = XmlConvert.TryFormat((ushort)o, _primitivesBuffer, out charsWritten); type = "unsignedShort"; break; case TypeCode.UInt32: - tryFormatResult = XmlConvert.TryFormat((uint)o, buffer, out charsWritten); + tryFormatResult = XmlConvert.TryFormat((uint)o, _primitivesBuffer, out charsWritten); type = "unsignedInt"; break; case TypeCode.UInt64: - tryFormatResult = XmlConvert.TryFormat((ulong)o, buffer, out charsWritten); + tryFormatResult = XmlConvert.TryFormat((ulong)o, _primitivesBuffer, out charsWritten); type = "unsignedLong"; break; @@ -351,19 +350,19 @@ protected void WriteTypedPrimitive(string? name, string? ns, object o, bool xsiT } else if (t == typeof(Guid)) { - tryFormatResult = XmlConvert.TryFormat((Guid)o, buffer, out charsWritten); + tryFormatResult = XmlConvert.TryFormat((Guid)o, _primitivesBuffer, out charsWritten); type = "guid"; typeNs = UrtTypes.Namespace; } else if (t == typeof(TimeSpan)) { - tryFormatResult = XmlConvert.TryFormat((TimeSpan)o, buffer, out charsWritten); + tryFormatResult = XmlConvert.TryFormat((TimeSpan)o, _primitivesBuffer, out charsWritten); type = "TimeSpan"; typeNs = UrtTypes.Namespace; } else if (t == typeof(DateTimeOffset)) { - tryFormatResult = XmlConvert.TryFormat((DateTimeOffset)o, buffer, out charsWritten); + tryFormatResult = XmlConvert.TryFormat((DateTimeOffset)o, _primitivesBuffer, out charsWritten); type = "dateTimeOffset"; typeNs = UrtTypes.Namespace; } @@ -382,12 +381,10 @@ protected void WriteTypedPrimitive(string? name, string? ns, object o, bool xsiT xmlNodes[i].WriteTo(_w); } _w.WriteEndElement(); - Interlocked.Exchange(ref _primitivesBuffer, buffer); return; } else { - Interlocked.Exchange(ref _primitivesBuffer, buffer); throw CreateUnknownTypeException(t); } @@ -413,11 +410,11 @@ protected void WriteTypedPrimitive(string? name, string? ns, object o, bool xsiT Debug.Assert(tryFormatResult.Value, "Something goes wrong with formatting primitives to the buffer."); #if DEBUG const string escapeChars = "<>\"'&"; - ReadOnlySpan span = buffer; + ReadOnlySpan span = _primitivesBuffer; Debug.Assert(span.Slice(0, charsWritten).IndexOfAny(escapeChars) == -1, "Primitive value contains illegal xml char."); #endif //all the primitive types except string and XmlQualifiedName writes to the buffer - _w.WriteRaw(buffer, 0, charsWritten); + _w.WriteRaw(_primitivesBuffer, 0, charsWritten); } else { @@ -432,7 +429,6 @@ protected void WriteTypedPrimitive(string? name, string? ns, object o, bool xsiT } _w.WriteEndElement(); - Interlocked.Exchange(ref _primitivesBuffer, buffer); } private string GetQualifiedName(string name, string? ns)