dotnet/wpf · error · XamlXmlWriterException

SR.ExpandPositionalParametersWithReadOnlyProperties

Error message

SR.ExpandPositionalParametersWithReadOnlyProperties

What it means

After matching a constructor parameter to a [ConstructorArgument] property, the writer checks that the property is writable; expansion works by inserting StartMember/EndMember assignment nodes, which requires a settable property. A read-only matched property triggers XamlXmlWriterException ExpandPositionalParametersWithReadOnlyProperties.

Solutions

  1. Add a setter (internal or private is not enough — make it settable) to the [ConstructorArgument] property.
  2. Remove [ConstructorArgument] from read-only properties and use a constructor overload the writer can expand.
  3. Re-expose the value via a settable backing property used only for XAML.
  4. Catch XamlXmlWriterException and write explicit member nodes with a different serialization strategy.

Example fix

// before
[ConstructorArgument("key")] public string Key { get; }
// after
[ConstructorArgument("key")] public string Key { get; set; }
Defensive patterns

Strategy: type-guard

Validate before calling

foreach (var prop in caaProps) if (prop.IsReadOnly) throw new InvalidOperationException($"[ConstructorArgument] property {prop.Name} must be settable");

Type guard

bool IsSettableCaaProp(PropertyInfo p) => p.GetSetMethod(nonPublic: false) is not null && p.GetCustomAttribute<ConstructorArgumentAttribute>() is not null;

Try / catch

try { writer.WriteEndObject(); } catch (XamlXmlWriterException ex) when (ex.Message.Contains("ReadOnly")) { /* serialize with explicit members instead */ }

Prevention

When it happens

Trigger: Expanding positional parameters where a matched [ConstructorArgument] property has IsReadOnly == true (get-only property, indexer, or read-only collection property).

Common situations: Immutable-style markup extensions exposing get-only properties with [ConstructorArgument], porting types that changed from settable to read-only properties, source-generated properties without setters.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of dotnet/wpf@81131a70a4 (2026-09-14). Data as JSON: /api/errors/745273afc6cda22a. Report an issue: GitHub.

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/System.Xaml/System/Xaml/XamlXmlWriter.cs:1951

                        if ((potentialProperty.Type.UnderlyingType == paraminfo.ParameterType) &&
                            (XamlObjectReader.GetConstructorArgument(potentialProperty) == paraminfo.Name))
                        {
                            matchingProperty = potentialProperty;
                            break;
                        }
                    }

                    if (matchingProperty is null)
                    {
                        throw new XamlXmlWriterException(SR.ConstructorNotFoundForGivenPositionalParameters);
                    }

                    XamlMember member = objectXamlType.GetMember(matchingProperty.Name);
                    Debug.Assert(member is not null);

                    if (member.IsReadOnly)
                    {
                        throw new XamlXmlWriterException(SR.ExpandPositionalParametersWithReadOnlyProperties);
                    }

                    writer.ppStateInfo.NodesList[i].Insert(0, new XamlNode(XamlNodeType.StartMember, member));
                    writer.ppStateInfo.NodesList[i].Add(new XamlNode(XamlNodeType.EndMember));
                }
            }

            private ParameterInfo[] GetParametersInfo(XamlType objectXamlType, int numOfParameters)
            {
                IList<XamlType> paramXamlTypes = objectXamlType.GetPositionalParameters(numOfParameters);

                if (paramXamlTypes is null)
                {
                    throw new XamlXmlWriterException(SR.ConstructorNotFoundForGivenPositionalParameters);
                }

                Type[] paramClrTypes = new Type[numOfParameters];

View on GitHub (pinned to 81131a70a4)