Fix reflection XmlSerializer choice identifier with XmlEnum aliases#130445
Conversation
The reflection-based XmlSerializer (ReflectionXmlSerializationReader)
resolved an [XmlChoiceIdentifier] enum value during deserialization by
comparing the choice enum member identifiers directly against the matched
XML element name. That only works when the enum member name equals the
element name, so a choice enum using [XmlEnum] aliases (e.g.
[XmlEnum("Number")] NumberChoice) failed to round-trip: the choice field
was left at its default instead of the value implied by the element.
Resolve the choice value positionally instead, using the element index
already computed when matching the element and reading the corresponding
choice.MemberIds[elementIndex]. This mirrors exactly how the IL-generated
reader resolves the choice and reuses the alias-aware MemberIds table that
XmlReflectionImporter builds up front, so [XmlEnum] aliases are honored.
Add a regression test (Xml_TypeWithAliasedChoiceIdentifier) plus the
supporting AliasedChoiceType enum and TypeWithAliasedChoiceIdentifier type,
covering the previously untested combination of [XmlChoiceIdentifier] with
[XmlEnum] aliases. The test runs against both the reflection reader and the
ReflectionOnly serializer configurations.
Fixes dotnet#130017
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR fixes a deserialization bug in the reflection-based XmlSerializer reader where [XmlChoiceIdentifier] values were derived by comparing enum member identifiers to XML element names (which breaks when the enum uses [XmlEnum] aliases). The reader now resolves the choice identifier by using the already-computed matched element index to select choice.MemberIds[elementIndex], aligning behavior with the IL-generated reader.
Changes:
- Update
ReflectionXmlSerializationReaderto set choice identifiers based on the matched element index (rather than element name string comparison). - Add new serialization test types covering
[XmlChoiceIdentifier]+[XmlEnum]alias usage. - Add a regression test that round-trips an aliased choice identifier and validates both the item and choice enum value.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| src/libraries/System.Private.Xml/src/System/Xml/Serialization/ReflectionXmlSerializationReader.cs | Switch choice identifier assignment to use positional MemberIds[elementIndex], ensuring alias-aware resolution matches ILGen behavior. |
| src/libraries/System.Runtime.Serialization.Xml/tests/SerializationTypes.RuntimeOnly.cs | Add AliasedChoiceType and TypeWithAliasedChoiceIdentifier test types to model aliased choice identifiers. |
| src/libraries/System.Private.Xml/tests/XmlSerializer/XmlSerializerTests.RuntimeOnly.cs | Add regression test validating round-trip of aliased choice identifier through SerializeAndDeserialize. |
|
Workflow state for the Holistic Review Orchestrator. {
"version": 5,
"last_dispatched_commit": "3d78a07b9f1df545070ca5011b899591cc3626dd",
"last_dispatched_base_ref": "main",
"last_dispatched_base_sha": "6c5849144dc8a23aa0760080d6f7784fed60da37",
"last_reviewed_commit": "3d78a07b9f1df545070ca5011b899591cc3626dd",
"last_reviewed_base_ref": "main",
"last_reviewed_base_sha": "6c5849144dc8a23aa0760080d6f7784fed60da37",
"last_recorded_worker_run_id": "29690568647",
"review_attempt_commit": "",
"review_attempt_base_ref": "",
"review_attempt_count": 0,
"max_review_attempts": 5,
"review_history_format": "holistic-review-disclosure-v1",
"review_history": [
{
"commit": "3d78a07b9f1df545070ca5011b899591cc3626dd",
"review_id": 4730919419
}
]
} |
There was a problem hiding this comment.
Holistic Review
Motivation: The reflection-based XmlSerializer (ReflectionXmlSerializationReader) resolved an [XmlChoiceIdentifier] enum value by comparing choice enum member identifiers against the matched XML element name. This only works when the enum member name equals the element name, so a choice enum using [XmlEnum] aliases (e.g. [XmlEnum("Number")] NumberChoice) failed to round-trip and left the choice field at its default. This addresses #130017.
Approach: The fix resolves the choice value positionally using the element index already computed while matching the element (choice.MemberIds[elementIndex]), instead of string-comparing enum member names. WriteElement gains an elementIndex parameter threaded from WriteMemberElementsIf, and Member.ChoiceSource changes from Action<object> (element name) to Action<int> (element index). This mirrors exactly how the IL-generated reader (XmlSerializationReaderILGen) resolves the choice and reuses the alias-aware MemberIds table that XmlReflectionImporter builds up front, so [XmlEnum] aliases are honored.
Summary: This is a well-targeted, minimal correctness fix that brings the reflection reader into parity with the IL-generated reader. Key points verified:
MemberIdsis built inXmlReflectionImporterparallel toaccessor.Elements, and the matching loop inWriteMemberElementsIfuses that same per-member element indexi, so the positional lookup is consistent with the table it indexes.- The bounds check
(uint)elementIndex >= (uint)memberIds.Lengthsafely handles the-1default (used by the recursiveWriteElementcall at line 1253, wherememberis null andChoiceSourceis never invoked anyway), avoiding anyIndexOutOfRangeException. - The any-element fallback path correctly passes
elementIndex: i. - The added regression test covers the previously untested
[XmlChoiceIdentifier]+[XmlEnum]alias combination and runs against both the reflection reader andReflectionOnlyconfigurations.
No actionable issues found. The change is correct, consistent with existing conventions, and well-tested.
Note
This review was generated by this repository's Holistic Review agentic workflow to complement the built-in Copilot review.
Generated by Holistic Review · 55.2 AIC · ⌖ 10.5 AIC · ⊞ 10K
The reflection-based XmlSerializer (ReflectionXmlSerializationReader) resolved an [XmlChoiceIdentifier] enum value during deserialization by comparing the choice enum member identifiers directly against the matched XML element name. That only works when the enum member name equals the element name, so a choice enum using [XmlEnum] aliases (e.g. [XmlEnum("Number")] NumberChoice) failed to round-trip: the choice field was left at its default instead of the value implied by the element.
Resolve the choice value positionally instead, using the element index already computed when matching the element and reading the corresponding choice.MemberIds[elementIndex]. This mirrors exactly how the IL-generated reader resolves the choice and reuses the alias-aware MemberIds table that XmlReflectionImporter builds up front, so [XmlEnum] aliases are honored.
Add a regression test (Xml_TypeWithAliasedChoiceIdentifier) plus the supporting AliasedChoiceType enum and TypeWithAliasedChoiceIdentifier type, covering the previously untested combination of [XmlChoiceIdentifier] with [XmlEnum] aliases. The test runs against both the reflection reader and the ReflectionOnly serializer configurations.
Fixes #130017