Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Serialize primitive types in ReflectionXmlSerializationWriter using char[]. #81687

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
0010a7b
Introduce TryFormats for almost all primitive types
TrayanZapryanov Sep 29, 2022
7a773e1
Use primitive char buffer in XmlSerializationWriter
TrayanZapryanov Sep 29, 2022
dcc6180
Fix char cast
TrayanZapryanov Sep 30, 2022
f0558e4
Add tests for different types
TrayanZapryanov Sep 30, 2022
0f1db0d
Add byte type
TrayanZapryanov Sep 30, 2022
99b2169
Address feedback
TrayanZapryanov Oct 24, 2022
6d5f5f6
Fix tests
TrayanZapryanov Oct 25, 2022
a51e952
remove using
TrayanZapryanov Oct 25, 2022
b7e9b9d
Increase duration char buffer size as it is not enough for TimeSpan.M…
TrayanZapryanov Oct 25, 2022
7a1a1dc
Address feedback
TrayanZapryanov Nov 2, 2022
2c90324
Merge branch 'dotnet:main' into improve_xmlserializatiowriter_writety…
TrayanZapryanov Nov 10, 2022
ed66817
Added assert if we cannot format primitive value to the suppiled buffer
TrayanZapryanov Nov 23, 2022
edef33e
Merge branch 'improve_xmlserializatiowriter_writetypedprimitive' of h…
TrayanZapryanov Nov 23, 2022
f3ca604
Lazy create primitives buffer
TrayanZapryanov Nov 24, 2022
7125e17
Merge branch 'dotnet:main' into improve_xmlserializatiowriter_writety…
TrayanZapryanov Dec 16, 2022
6a3b784
Address new feadback
TrayanZapryanov Jan 22, 2023
80f2007
Merge branch 'improve_xmlserializatiowriter_writetypedprimitive' of h…
TrayanZapryanov Jan 22, 2023
f4f5a9e
Resolve feedback
TrayanZapryanov Jan 24, 2023
2b9e4ee
Merge branch 'dotnet:main' into improve_xmlserializatiowriter_writety…
TrayanZapryanov Jan 24, 2023
b8582e1
Optimize float and double TryFormat
TrayanZapryanov Jan 25, 2023
12a51de
Replace ArrayPool renting with Interlocked. Fix Debug.Assert
TrayanZapryanov Feb 4, 2023
135744e
Use primitives buffer in ReflectionXmlSerializationWriter
TrayanZapryanov Feb 6, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,8 @@ private enum XsdDateTimeKind
private static ReadOnlySpan<int> DaysToMonth366 => new int[] {
0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366};

private const int CharStackBufferSize = 64;

/// <summary>
/// Constructs an XsdDateTime from a string using specific format.
/// </summary>
Expand Down Expand Up @@ -495,7 +497,17 @@ public static implicit operator DateTimeOffset(XsdDateTime xdt)
/// </summary>
public override string ToString()
{
var vsb = new ValueStringBuilder(stackalloc char[64]);
Span<char> destination = stackalloc char[CharStackBufferSize];
bool success = TryFormat(destination, out int charsWritten);
Debug.Assert(success);

return destination.Slice(0, charsWritten).ToString();
}

public bool TryFormat(Span<char> destination, out int charsWritten)
{
var vsb = new ValueStringBuilder(destination);

switch (InternalTypeCode)
{
case DateTimeTypeCode.DateTime:
Expand Down Expand Up @@ -534,7 +546,9 @@ public override string ToString()
break;
}
PrintZone(ref vsb);
return vsb.ToString();

charsWritten = vsb.Length;
return destination.Length >= vsb.Length;
}

// Serialize year, month and day
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = 32;

private enum Parts
{
Expand Down Expand Up @@ -341,7 +342,16 @@ public override string ToString()
/// </summary>
internal string ToString(DurationType durationType)
{
var vsb = new ValueStringBuilder(stackalloc char[20]);
Span<char> destination = stackalloc char[CharStackBufferSize];
bool success = TryFormat(destination, out int charsWritten, durationType);
Debug.Assert(success);

return destination.Slice(0, charsWritten).ToString();
}

public bool TryFormat(Span<char> destination, out int charsWritten, DurationType durationType = DurationType.Duration)
{
var vsb = new ValueStringBuilder(destination);
int nanoseconds, digit, zeroIdx, len;

if (IsNegative)
Expand Down Expand Up @@ -411,7 +421,9 @@ internal string ToString(DurationType durationType)
}

vsb.EnsureCapacity(zeroIdx + 1);
vsb.Append(tmpSpan.Slice(0, zeroIdx - len + 1));
int nanoSpanLength = zeroIdx - len + 1;
bool successCopy = tmpSpan[..nanoSpanLength].TryCopyTo(vsb.AppendSpan(nanoSpanLength));
Debug.Assert(successCopy);
}
vsb.Append('S');
}
Expand All @@ -428,7 +440,8 @@ internal string ToString(DurationType durationType)
vsb.Append("0M");
}

return vsb.ToString();
charsWritten = vsb.Length;
return destination.Length >= vsb.Length;
}

internal static Exception? TryParse(string s, out XsdDuration result)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,11 @@ private void WriteText(object o, TextAccessor text)
}
else
{
if (TryWritePrimitiveValue(primitiveMapping.TypeDesc!, o))
{
return;
}

if (!WritePrimitiveValue(primitiveMapping.TypeDesc!, o, out stringValue))
{
Debug.Assert(o is byte[]);
Expand Down Expand Up @@ -808,6 +813,10 @@ private void WriteMember(object? memberValue, AttributeAccessor attribute, TypeD
}
else
{
if (TryWritePrimitiveValue(arrayElementTypeDesc!, ai))
{
continue;
}
if (!WritePrimitiveValue(arrayElementTypeDesc!, ai, out stringValue))
{
Debug.Assert(ai is byte[]);
Expand Down Expand Up @@ -983,6 +992,10 @@ private void WritePrimitive(WritePrimitiveMethodRequirement method, string name,
}
else
{
if (TryWritePrimitiveValue(method, name, ns, typeDesc, o, xmlQualifiedName))
{
return;
}
hasValidStringValue = WritePrimitiveValue(typeDesc, o, out stringValue);
}

Expand Down Expand Up @@ -1191,6 +1204,131 @@ private static string ConvertPrimitiveToString(object o, TypeDesc typeDesc)
return stringValue;
}

private static bool TryFormatPrimitiveValue(string? formatterName, bool hasCustomFormatter, object? o, Span<char> destination, out int charsWritten)
{
charsWritten = 0;
if (o == null || formatterName == "String")
{
return false;
}

if (!hasCustomFormatter)
{
switch (formatterName)
{
case "Boolean":
return XmlConvert.TryFormat((bool)o, destination, out charsWritten);
case "Int32":
return XmlConvert.TryFormat((int)o, destination, out charsWritten);
case "Int16":
return XmlConvert.TryFormat((short)o, destination, out charsWritten);
case "Int64":
return XmlConvert.TryFormat((long)o, destination, out charsWritten);
case "Single":
return XmlConvert.TryFormat((float)o, destination, out charsWritten);
case "Double":
return XmlConvert.TryFormat((double)o, destination, out charsWritten);
case "Decimal":
return XmlConvert.TryFormat((decimal)o, destination, out charsWritten);
case "Byte":
return XmlConvert.TryFormat((byte)o, destination, out charsWritten);
case "SByte":
return XmlConvert.TryFormat((sbyte)o, destination, out charsWritten);
case "UInt16":
return XmlConvert.TryFormat((ushort)o, destination, out charsWritten);
case "UInt32":
return XmlConvert.TryFormat((uint)o, destination, out charsWritten);
case "UInt64":
return XmlConvert.TryFormat((ulong)o, destination, out charsWritten);
// Types without direct mapping (ambiguous)
case "Guid":
return XmlConvert.TryFormat((Guid)o, destination, out charsWritten);
case "Char":
return XmlConvert.TryFormat((char)o, destination, out charsWritten);
case "TimeSpan":
return XmlConvert.TryFormat((TimeSpan)o, destination, out charsWritten);
case "DateTimeOffset":
return XmlConvert.TryFormat((DateTimeOffset)o, destination, out charsWritten);
default:
return false;
}
}

switch (o)
{
case DateTime dt when formatterName == "DateTime":
return TryFormatDateTime(dt, destination, out charsWritten);
case DateTime d when formatterName == "Date":
return TryFormatDate(d, destination, out charsWritten);
case DateTime t when formatterName == "Time":
return TryFormatTime(t, destination, out charsWritten);
case DateTime:
throw new InvalidOperationException(SR.Format(SR.XmlInternalErrorDetails, "Invalid DateTime"));
case char c when formatterName == "Char":
return TryFormatChar(c, destination, out charsWritten);
default:
return false;
}
}

private bool TryWritePrimitiveValue(WritePrimitiveMethodRequirement method, string name, string? ns, TypeDesc typeDesc, object? o, XmlQualifiedName? xmlQualifiedName)
{
if (typeDesc == ReflectionXmlSerializationReader.StringTypeDesc
|| hasRequirement(method, WritePrimitiveMethodRequirement.WriteNullableStringLiteral)) return false;

char[] buffer = RentPrimitivesBuffer();
bool result = TryFormatPrimitiveValue(typeDesc.FormatterName, typeDesc.HasCustomFormatter, o, buffer, out int charsWritten);
if (result)
{
if (hasRequirement(method, WritePrimitiveMethodRequirement.WriteElementString))
{
WriteElementRaw(name, ns, buffer, 0, charsWritten, xmlQualifiedName);
}
else if (hasRequirement(method, WritePrimitiveMethodRequirement.WriteAttribute))
{
WriteAttribute(name, ns, buffer, 0, charsWritten);
}
else
{
Debug.Fail("https://github.com/dotnet/runtime/issues/18037: Add More Tests for Serialization Code");
}
}

ReturnPrimitivesBuffer(buffer);
return result;
}

private bool TryWritePrimitiveValue(TypeDesc typeDesc, object? o)
{
if (typeDesc == ReflectionXmlSerializationReader.StringTypeDesc) return false;

char[] buffer = RentPrimitivesBuffer();
bool result = TryFormatPrimitiveValue(typeDesc.FormatterName, typeDesc.HasCustomFormatter, o, buffer, out int charsWritten);
if (result)
WriteValue(buffer, 0, charsWritten);

ReturnPrimitivesBuffer(buffer);
return result;
}

private static bool TryFormatTime(DateTime value, Span<char> destination, out int charsWritten)
{
if (!LocalAppContextSwitches.IgnoreKindInUtcTimeSerialization && value.Kind == DateTimeKind.Utc)
return XmlConvert.TryFormat(DateTime.MinValue + value.TimeOfDay, "HH:mm:ss.fffffffZ", destination, out charsWritten);

return XmlConvert.TryFormat(DateTime.MinValue + value.TimeOfDay, "HH:mm:ss.fffffffzzzzzz", destination, out charsWritten);
}

private static bool TryFormatDate(DateTime value, Span<char> destination, out int charsWritten)
{
return XmlConvert.TryFormat(value, "yyyy-MM-dd", destination, out charsWritten);
}

private static bool TryFormatChar(char value, Span<char> destination, out int charsWritten)
{
return XmlConvert.TryFormat((ushort)value, destination, out charsWritten);
}

[RequiresUnreferencedCode("calls WritePotentiallyReferencingElement")]
private void GenerateMembersElement(object o, XmlMembersMapping xmlMembersMapping)
{
Expand Down
Loading