dotnet/wpf · error · InvalidOperationException

SR.AttachedPropertyOnDictionaryKey

Error message

SR.AttachedPropertyOnDictionaryKey

What it means

Same invariant as the property case, but for dictionary keys: XamlObjectReader serializes dictionary keys as strings/type-converted values, and if a key object has attached properties set (GetAttachedProperties returns non-null) those would be lost in the x:Key representation. The reader throws an InvalidOperationException naming the key value and the attached property.

Solutions

  1. Use simple immutable values (string, int, enum) as dictionary keys instead of objects carrying attached properties.
  2. Clear the attached-property values from the key object before serialization (call ClearValue for each attached property).
  3. Restructure the data so the rich object becomes a value inside the entry rather than the key.

Example fix

// before
key = new FrameworkElement();
Canvas.SetTop(key, 5);
dictionary[key] = value; // key serialized via x:Key → throws
// after
key = "myKey"; // use a simple key without attached properties
dictionary[key] = value;
Defensive patterns

Strategy: validation

Validate before calling

if (key is DependencyObject d) {
  if (d.ReadLocalValue(Canvas.LeftProperty) != DependencyProperty.UnsetValue)
    throw new InvalidOperationException("Key has attached properties; use a simple key.");
}

Type guard

bool IsSimpleKey(object k) => k is string || k is int || k is long || k is Enum || k is Type;

Try / catch

try { SerializeDictionary(dict); }
catch (InvalidOperationException ex) when (ex.Message.Contains("attached")) { /* rebuild dict with simple keys */ }

Prevention

When it happens

Trigger: Reading an IDictionary where the key object (serialized via x:Key, property == null in ThrowIfPropertiesAreAttached) exposes attached properties — XamlObjectReader.cs:910-912.

Common situations: Using UI elements or rich objects as dictionary keys while attached properties (Grid.Row, Canvas.Left, AutomationProperties) were set on them; keyed ResourceDictionary-like structures serialized with XamlObjectReader.

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/7b96897f75f68788. Report an issue: GitHub.

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/System.Xaml/System/Xaml/XamlObjectReader.cs:912

                        valueInfo = ObjectMarkupInfo.ForObject(propertyValue, context, propertyConverter);
                    }
                }

                return valueInfo;
            }

            private static void ThrowIfPropertiesAreAttached(object value, XamlMember property, SerializerContext context)
            {
                var props = context.Runtime.GetAttachedProperties(value);
                if (props is not null)
                {
                    if (property is not null)
                    {
                        throw new InvalidOperationException(SR.Format(SR.AttachedPropertyOnTypeConvertedOrStringProperty, property.Name, value.ToString(), props[0].Key.ToString()));
                    }
                    else
                    {
                        throw new InvalidOperationException(SR.Format(SR.AttachedPropertyOnDictionaryKey, value.ToString(), props[0].Key.ToString()));
                    }
                }
            }

            // Reproduce the logic of System.ComponentModel.ReflectPropertyDescriptor.ShouldSerializeValue
            private static bool ShouldWriteProperty(object source, XamlMember property, SerializerContext context)
            {
                bool isReadOnly = !context.IsPropertyWriteVisible(property);

                if (!isReadOnly)
                {
                    object defaultValue;
                    if (GetDefaultValue(property, out defaultValue))
                    {
                        object actualValue = context.Runtime.GetValue(source, property);
                        return !Equals(defaultValue, actualValue);
                    }
                }

View on GitHub (pinned to 81131a70a4)