dotnet/wpf · error · InvalidOperationException

SR.SetOnReadOnlyObjectNotAllowed

Error message

SR.SetOnReadOnlyObjectNotAllowed

What it means

DependencyObject.SetValueCommon — the common path for SetValue, SetCurrentValue, SetDeferredValue and friends — throws InvalidOperationException(SR.SetOnReadOnlyObjectNotAllowed, this) when IsSealed is true. A DependencyObject becomes sealed (read-only) when it is being used by the property system itself, e.g. a Style/Template or a frozen Freezable-like state; sealed objects cannot have any property values set.

Solutions

  1. Check IsSealed before mutation; if sealed, call Clone()/CloneCurrentValue() (for Freezables) or create a new instance and modify the copy.
  2. Unapply the object (e.g. remove the Style from controls) before editing, then reapply.
  3. Design read-only data as immutable from the start so sealing doesn't surprise callers.

Example fix

// before
if (myStyle.Setters.Count == 0)
    myStyle.Setters.Add(new Setter(...)); // may throw if sealed
// after
if (myStyle.IsSealed)
    myStyle = myStyle.Clone();
myStyle.Setters.Add(new Setter(...));
Defensive patterns

Strategy: type-guard

Validate before calling

if (obj.IsSealed)
    obj = ((Freezable)obj).Clone(); // or construct a new instance

Type guard

bool CanMutate(DependencyObject o) => o is { IsSealed: false };

Try / catch

try { style.Setters.Add(setter); }
catch (InvalidOperationException ex) when (ex.Message.Contains("sealed") || ex.Message.Contains("read-only"))
{
    style = style.Clone();
    style.Setters.Add(setter);
}

Prevention

When it happens

Trigger: Calling SetValue/SetCurrentValue on a DependencyObject after it was sealed — e.g. mutating a Style's Setters while the style is applied, modifying a FrameworkTemplate in use, or setting values on an object returned sealed by an API.

Common situations: Editing styles/templates at runtime while in use; sharing one DependencyObject across controls after it was sealed; copying a sealed object by reference instead of cloning.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/DependencyObject.cs:627

        }

        /// <summary>
        ///     The common code shared by all variants of SetValue
        /// </summary>
        // Takes metadata from caller because most of them have already retrieved it
        //  for their own purposes, avoiding the duplicate GetMetadata call.
        private void SetValueCommon(
            DependencyProperty  dp,
            object              value,
            PropertyMetadata    metadata,
            bool                coerceWithDeferredReference,
            bool                coerceWithCurrentValue,
            OperationType       operationType,
            bool                isInternal)
        {
            if (IsSealed)
            {
                throw new InvalidOperationException(SR.Format(SR.SetOnReadOnlyObjectNotAllowed, this));
            }

            Expression newExpr = null;
            DependencySource[] newSources = null;

            EntryIndex entryIndex = LookupEntry(dp.GlobalIndex);

            // Treat Unset as a Clear
            if( value == DependencyProperty.UnsetValue )
            {
                Debug.Assert(!coerceWithCurrentValue, "Don't call SetCurrentValue with UnsetValue");
                // Parameters should have already been validated, so we call
                //  into the private method to avoid validating again.
                ClearValueCommon(entryIndex, dp, metadata);
                return;
            }

            // Validate the "value" against the DP.

View on GitHub (pinned to 81131a70a4)