Skip to content

Add Separator property to XmlTextAttribute and XmlAttributeAttribute for configurable list serialization#126767

Draft
Copilot wants to merge 2 commits intomainfrom
copilot/add-xmltextattribute-separator-property
Draft

Add Separator property to XmlTextAttribute and XmlAttributeAttribute for configurable list serialization#126767
Copilot wants to merge 2 commits intomainfrom
copilot/add-xmltextattribute-separator-property

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 10, 2026

[XmlText] string[] has always concatenated array items with no separator (val1val2), while [XmlAttribute] string[] uses space separation (val1 val2). This inconsistency is intentional and tested — changing the default would be breaking. This PR adds an opt-in Separator property to both attributes so users can control the separator character.

API Changes

XmlTextAttribute — new char Separator { get; set; } (default '\0' = no separator, preserves existing concatenation behavior):

// opt-in: space-separated text content, round-trips correctly
[XmlText(Separator = ' ')]
public string[] Items { get; set; }

// opt-in: comma-separated
[XmlText(Separator = ',')]
public string[] Tags { get; set; }

XmlAttributeAttribute — same char Separator { get; set; } (default '\0' = use existing space behavior):

// override attribute separator to comma
[XmlAttribute(Separator = ',')]
public string[] Values { get; set; }

'\0' (null char) is the "not set" sentinel — chosen because char? is not valid as a C# attribute argument type (CS0655).

Implementation

  • XmlTextAttribute / XmlAttributeAttribute: add char Separator property
  • Mappings.cs: add char? Separator to TextAccessor and AttributeAccessor (internal; char? is valid here)
  • XmlReflectionImporter: wire attribute → accessor, validate with XmlConvert.VerifyXmlChars() (rejects XML-illegal chars like \x01; '\0' sentinel skips validation)
  • Writers (ReflectionXmlSerializationWriter, XmlSerializationWriter, XmlSerializationWriterILGen): when TextAccessor.Separator.HasValue, emit items with separator between them; for attributes, use Separator ?? ' '
  • Readers (ReflectionXmlSerializationReader, XmlSerializationReader, XmlSerializationReaderILGen): when TextAccessor.Separator.HasValue, split text on separator and populate array; uses string.Split(char) overload (not char[]) for IL-gen compatibility
  • System.Xml.ReaderWriter ref assembly: updated with new public surface

Backward Compatibility

  • [XmlText] string[] with no Separator → unchanged concatenation (val1val2)
  • [XmlAttribute] string[] with no Separator override → unchanged space separation (val1 val2)
  • Existing test XML_TypeWithXmlTextAttributeOnArray continues to assert val1val2
Original prompt

Context

Issue #115837 reports that [XmlText] string[] on element content serializes array items concatenated without any separator (abcd), while [XmlAttribute] string[] serializes them space-separated (a b c d). This inconsistency has existed since .NET Framework and is the documented/tested behavior — changing the default would be a breaking change.

Design Decision (from discussion with area owner)

Rather than adding a simple IsList boolean, the solution is to add a char? Separator property to both XmlTextAttribute and XmlAttributeAttribute, allowing users to opt into list-style serialization with a configurable separator character.

Key design points:

  1. XmlTextAttribute.Separator — defaults to null (meaning no separator, preserving current concatenation behavior of [XmlText] string[]). Setting e.g. Separator = ' ' opts into space-separated list serialization for element text content.

  2. XmlAttributeAttribute.Separator — defaults to ' ' (preserving existing space-separated behavior for [XmlAttribute] string[]). Users can override to a different separator if desired.

  3. Type should be char? (nullable char), where null means "no separator / use default behavior". This eliminates multi-character separator edge cases and is simple to validate and emit.

  4. Validation: Use XmlConvert.VerifyXmlChars() on the separator character at reflection/import time (in XmlReflectionImporter). The XmlWriter layer already handles escaping of characters like <, &, > etc. in both attribute values and text content, so those are safe — they'll be entity-escaped in the output. The only truly dangerous characters are those illegal in XML 1.0 entirely (like \0), which VerifyXmlChars catches.

Implementation Guide

Files that need changes:

Public API surface:

  • src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlTextAttribute.cs — Add public char? Separator { get; set; } property (default: null)
  • src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlAttributes.cs — The XmlAttributeAttribute class needs the same public char? Separator { get; set; } property (default: ' ')

Internal mapping infrastructure:

  • src/libraries/System.Private.Xml/src/System/Xml/Serialization/Mappings.csTextAccessor is currently an empty class (internal sealed class TextAccessor : Accessor { }). Add an IsList property (or Separator property) mirroring what AttributeAccessor already has with its IsList bool. Consider whether to store the separator char itself or just a bool here.

Reflection import (wiring up the attribute to the mapping):

  • src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlReflectionImporter.cs — In ImportAccessorMapping, around line 1630-1640, where [XmlText] on array-like types creates a TextAccessor, wire up the separator from the attribute to the accessor. Validate the separator character here using XmlConvert.VerifyXmlChars(). Also around line 1595, where isList is computed for attributes, incorporate the new Separator property from XmlAttributeAttribute.

Serialization writers (emitting the separator during write):

  • src/libraries/System.Private.Xml/src/System/Xml/Serialization/ReflectionXmlSerializationWriter.cs — In WriteMember, the attribute IsList path already writes " " between values. Add analogous logic for TextAccessor when it has a separator.
  • src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriter.cs — Similar changes for the non-reflection writer path.
  • src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriterILGen.cs — IL-gen'd serializer path needs the same logic.

Deserialization readers (splitting on the separator during read):

  • The reader paths need to split text content on the separator character when deserializing back to an array. Look at how attribute IsList deserialization currently splits on whitespace and apply similar logic for text content.
  • src/libraries/System.Private.Xml/src/System/Xml/Serialization/ReflectionXmlSerializationReader.cs
  • src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationReader.cs
  • src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationReaderILGen.cs

What NOT to change:

  • The existing [XmlAttribute] string[] default behavior must remain space-separated (backward compatible)
  • The existing [XmlText] string[] default behavior must remain concatenated with no separator (backward compatible)
  • The existing test XML_TypeWithXmlTextAttributeOnArray asserts val1val2 concatenation — this must continue to pass

Tests to add:

  • [XmlText(Separator = ' ')] string[] round-trips as space-separated text content
  • [XmlText(Separator = ',')] string[] round-trips with comma separation
  • [XmlText] string[] (no separa...

This pull request was created from Copilot chat.

…for configurable list serialization

Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/68db653f-3600-4ba3-b399-78995d7c9d4b

Co-authored-by: StephenMolloy <19562826+StephenMolloy@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants