dotnet/wpf · error · ArgumentException

SR.Format(SR.DefaultValueMustBeFreeThreaded, propertyName)

Error message

SR.Format(SR.DefaultValueMustBeFreeThreaded, propertyName)

What it means

For properties whose type is DispatcherObject-derived, a default value carrying thread affinity (Dispatcher != null) cannot be shared across threads, and if it is neither free-threaded nor ISealable, WPF rejects it. Only free-threaded or sealable defaults are accepted so the metadata can be safely shared across all uses.

Solutions

  1. Freeze the default Freezable before putting it in metadata (call Freeze() so it becomes free-threaded).
  2. Use a shared static default created once and frozen.
  3. Choose a plain type without thread affinity (e.g. Color instead of SolidColorBrush) if possible.

Example fix

// before
var brush = new SolidColorBrush(Colors.Red);
new PropertyMetadata(brush);
// after
var brush = new SolidColorBrush(Colors.Red);
brush.Freeze();
new PropertyMetadata(brush);
Defensive patterns

Strategy: validation

Validate before calling

if (defaultValue is DispatcherObject d && d.Dispatcher != null && defaultValue is not ISealable) throw new ArgumentException("Default must be free-threaded");

Type guard

static bool IsFreeThreadedDefault(object v) => v is not DispatcherObject d || d.Dispatcher == null || v is Freezable f && f.IsFrozen;

Prevention

When it happens

Trigger: Registering a property of type e.g. Brush/Geometry/Freezable-derived with a default instance that has a Dispatcher (created on the UI thread) and is not frozen/sealable in metadata.

Common situations: Passing a live UI-created Brush (e.g. new SolidColorBrush picked up from a control) as a default; defaults created on a background thread then used in multi-threaded scenarios.

Related errors


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

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/DependencyProperty.cs:423

                if (defaultValue is DispatcherObject dispatcherObject && dispatcherObject.Dispatcher != null)
                {
                    // Try to make the DispatcherObject free-threaded if it's an
                    // ISealable.


                    if (dispatcherObject is ISealable valueAsISealable && valueAsISealable.CanSeal)
                    {
                        Invariant.Assert(!valueAsISealable.IsSealed,
                               "A Sealed ISealable must not have dispatcher affinity");

                        valueAsISealable.Seal();

                        Invariant.Assert(dispatcherObject.Dispatcher == null,
                            "ISealable.Seal() failed after ISealable.CanSeal returned true");
                    }
                    else
                    {
                        throw new ArgumentException(SR.Format(SR.DefaultValueMustBeFreeThreaded, propertyName));
                    }
                }
            }


            // After checking for correct type, check default value against
            //  validator (when one is given)
            if ( validateValueCallback != null &&
                !validateValueCallback(defaultValue))
            {
                throw new ArgumentException(SR.Format(SR.DefaultValueInvalid, propertyName));
            }
        }


        /// <summary>
        ///     Parameter validation for OverrideMetadata, includes code to force
        /// all base classes of "forType" to register their metadata so we know

View on GitHub (pinned to 81131a70a4)