Skip to content

Commit

Permalink
Improve XmlSerializationWriter.WriteTypedPrimitive (#76436)
Browse files Browse the repository at this point in the history
* Introduce TryFormats for almost all primitive types

* Use primitive char buffer in XmlSerializationWriter

* Fix char cast

* Add tests for different types

* Add byte type

* Address feedback

* Fix tests

* remove using

* Increase duration char buffer size as it is not enough for TimeSpan.Max/Min

* Address feedback

* Added assert if we cannot format primitive value to the suppiled buffer

* Lazy create primitives buffer

* Address new feadback

* Resolve feedback

* Optimize float and double TryFormat

* Replace ArrayPool renting with Interlocked. Fix Debug.Assert

* Do not expect concurrency when using primitives buffer

---------

Co-authored-by: Traian Zaprianov <[email protected]>
  • Loading branch information
TrayanZapryanov and TrayanZapryanov authored Mar 31, 2023
1 parent 5eeb91f commit b79adc5
Show file tree
Hide file tree
Showing 6 changed files with 308 additions and 31 deletions.
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 @@ -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[64];

// this method must be called before any generated serialization methods are called
internal void Init(XmlWriter w, XmlSerializerNamespaces? namespaces, string? encodingStyle, string? idBase)
{
Expand Down Expand Up @@ -120,6 +123,11 @@ protected static string FromDateTime(DateTime value)
return XmlCustomFormatter.FromDateTime(value);
}

internal static bool TryFormatDateTime(DateTime value, Span<char> destination, out int charsWritten)
{
return XmlCustomFormatter.TryFormatDateTime(value, destination, out charsWritten);
}

protected static string FromDate(DateTime value)
{
return XmlCustomFormatter.FromDate(value);
Expand Down Expand Up @@ -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))
{
Expand All @@ -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)(char)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;

Expand All @@ -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;
}
Expand All @@ -374,7 +384,10 @@ protected void WriteTypedPrimitive(string? name, string? ns, object o, bool xsiT
return;
}
else
{
throw CreateUnknownTypeException(t);
}

break;
}
if (!wroteStartElement)
Expand All @@ -387,21 +400,34 @@ 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);
Debug.Assert(tryFormatResult.Value, "Something goes wrong with formatting primitives to the buffer.");
#if DEBUG
const string escapeChars = "<>\"'&";
ReadOnlySpan<char> 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(_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();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,17 @@ internal static string FromDateTime(DateTime value)
}
}

internal static bool TryFormatDateTime(DateTime value, Span<char> 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);
Expand Down
Loading

0 comments on commit b79adc5

Please sign in to comment.