diff --git a/.agents/skills/polyxml-codegen-workflow/SKILL.md b/.agents/skills/polyxml-codegen-workflow/SKILL.md
index 0467fb9..02179e0 100644
--- a/.agents/skills/polyxml-codegen-workflow/SKILL.md
+++ b/.agents/skills/polyxml-codegen-workflow/SKILL.md
@@ -815,3 +815,9 @@ Jackson 3 sorts properties alphabetically by default. Emit `@JsonPropertyOrder`
from flattened schema fields for its struct models, including inherited fields,
or valid reads can produce XSD-invalid XML on writes. The Spring fixture checks
returned XML against the source XSD independently using the JDK validator.
+
+C# positional record properties named Equals/GetHashCode/ToString collide with
+synthesized methods (UCI QueryPET's Equals field exposed CS8866). Suffix their
+identifiers with Value before applying per-struct uniqueness; preserve original
+XML names. The particle wire round-trip test covers these names and an explicit
+EqualsValue collision in both record and mutable-class modes.
diff --git a/crates/polyxml-core/src/codegen/csharp/mod.rs b/crates/polyxml-core/src/codegen/csharp/mod.rs
index e9b7501..e4e9a7d 100644
--- a/crates/polyxml-core/src/codegen/csharp/mod.rs
+++ b/crates/polyxml-core/src/codegen/csharp/mod.rs
@@ -114,6 +114,12 @@ pub fn to_csharp_property_name(raw: &str, enclosing_type: Option<&str>) -> Strin
};
let sanitized = sanitize_keyword(&safe, "csharp");
+ // Positional record members cannot shadow the synthesized equality methods.
+ let sanitized = if matches!(sanitized.as_str(), "Equals" | "GetHashCode" | "ToString") {
+ format!("{sanitized}Value")
+ } else {
+ sanitized
+ };
if let Some(enclosing) = enclosing_type {
if sanitized == enclosing || sanitized.trim_start_matches('@') == enclosing {
return format!("{}Value", sanitized);
diff --git a/crates/polyxml-core/tests/test_csharp_codegen.rs b/crates/polyxml-core/tests/test_csharp_codegen.rs
index e3064e8..91140a9 100644
--- a/crates/polyxml-core/tests/test_csharp_codegen.rs
+++ b/crates/polyxml-core/tests/test_csharp_codegen.rs
@@ -79,6 +79,7 @@ static DOTNET_LOCK: Mutex<()> = Mutex::new(());
#[test]
fn particle_wire_structure_round_trips() {
for (schema, root, documents) in [
+ (r#""#, "Query", vec!["abcd"]),
(include_str!("../../../research/fixtures/nested_sequence_choice.xsd"), "Root", vec!["AB", "C"]),
(include_str!("../../../research/fixtures/repeated_sequence.xsd"), "Root", vec!["A1B1A2B2"]),
(include_str!("../../../research/fixtures/substitution_group.xsd"), "Portfolio", vec!["A1B1A2"]),