dotnet/wpf · error · InvalidOperationException

SR.Format(SR.Freezable_CantBeFrozen, GetType().FullName)

Error message

SR.Format(SR.Freezable_CantBeFrozen, GetType().FullName)

What it means

Freezable.WritePreamble is called at the start of every mutating API to enforce thread affinity and immutability. If the Freezable is already frozen (IsFrozenInternal), any mutation attempt throws InvalidOperationException with Freezable_CantBeFrozen naming the concrete type — frozen objects must never be modified.

Solutions

  1. Clone the Freezable with Clone()/CloneCurrentValue() and mutate the unfrozen copy
  2. Keep an unfrozen instance if mutation is expected later
  3. Check IsFrozen before mutating and branch to the clone path
  4. Share frozen instances only as read-only resources

Example fix

// before
sharedBrush.Color = Colors.Blue; // frozen -> throws
// after
var copy = sharedBrush.Clone();
copy.Color = Colors.Blue;
copy.Freeze();
Defensive patterns

Strategy: type-guard

Validate before calling

if (obj.IsFrozen) { obj = obj.Clone(); } obj.Color = Colors.Blue; // mutate copy

Type guard

static bool IsMutable(System.Windows.Freezable f) => !f.IsFrozen;

Try / catch

try { obj.Color = Colors.Blue; }
catch (InvalidOperationException ex) when (obj.IsFrozen) { var copy = obj.Clone(); copy.Color = Colors.Blue; }

Prevention

When it happens

Trigger: Calling any setter or mutating method (Color, Geometry data, collection Add/Remove, etc.) on a Freezable that has been frozen via Freeze(), GetAsFrozen, or implicit XAML freezing.

Common situations: Keeping a cached frozen resource (e.g. Brushes.Red or a frozen Brush from a shared ResourceDictionary) and trying to change its Color; mutating a template/animation object after it was frozen for cross-thread use; deserializing into a frozen instance.

Related errors


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

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Freezable.cs:616

        /// This ensures that the object is being accessed from a valid thread.
        /// </summary>
        protected void ReadPreamble()
        {
            VerifyAccess();
        }

        /// <summary>
        /// Extenders of Freezable must call this method prior to changing the state
        /// of the object (e.g. the beginning of a property setter.)  This ensures that
        /// the object is not frozen and is being accessed from a valid thread.
        /// </summary>
        protected void WritePreamble()
        {
            VerifyAccess();

            if (IsFrozenInternal)
            {
                throw new InvalidOperationException(
                    SR.Format(SR.Freezable_CantBeFrozen,GetType().FullName));
            }
        }

        /// <summary>
        /// Extenders of Freezable must call this method at the end of an API which
        /// changed the state of the object (e.g., at the end of a property setter) to
        /// raise the Changed event.  Multiple state changes within a method or
        /// property may be "batched" into a single call to WritePostscript().
        /// </summary>
        protected void WritePostscript()
        {
            FireChanged();
        }

        /// <summary>
        /// Extenders of Freezable call this to set in a new value for internal
        /// properties or other embedded values that themselves are DependencyObjects.

View on GitHub (pinned to 81131a70a4)