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
- Check IsSealed before mutation; if sealed, call Clone()/CloneCurrentValue() (for Freezables) or create a new instance and modify the copy.
- Unapply the object (e.g. remove the Style from controls) before editing, then reapply.
- 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
- Check IsSealed before mutating any DependencyObject, especially Styles/Templates.
- Clone (Freezable.Clone) instead of mutating shared instances.
- Do styling mutations before the style is applied to controls.
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
- SR.CannotChangeAfterSealed
- SR.CannotChangeAfterSealed
- Animation_Invalid_DefaultValue
- Cannot remove signature from read-only file.
- Image_EncoderNoColorContext
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)