dotnet/wpf · error · InvalidOperationException

SR.Format(SR.StyleBasedOnHasLoop)

Error message

SR.Format(SR.StyleBasedOnHasLoop)

What it means

CheckForCircularBasedOnReferences walks the BasedOn chain during Seal and detects a style that appears twice in the hierarchy, i.e. a cycle (A BasedOn B BasedOn ... BasedOn A). Cycles would cause infinite recursion during style resolution, so Seal throws InvalidOperationException (SR.StyleBasedOnHasLoop).

Solutions

  1. Break the cycle: make one style's BasedOn null or point it at a genuinely separate base style.
  2. Construct style hierarchies bottom-up (base styles first) and never re-parent a style that is already an ancestor.
  3. Add an assertion/loop check in code that assigns BasedOn dynamically.

Example fix

// before
styleA.BasedOn = styleB;
styleB.BasedOn = styleA; // cycle
// after
styleA.BasedOn = null;
styleB.BasedOn = styleA;
Defensive patterns

Strategy: validation

Validate before calling

var seen = new HashSet<Style>();
for (var s = style.BasedOn; s != null; s = s.BasedOn)
    if (!seen.Add(s)) throw new InvalidOperationException("BasedOn cycle detected before Seal.");

Type guard

static bool HasBasedOnLoop(Style s) { var seen = new HashSet<Style>(); for (var b = s.BasedOn; b != null; b = b.BasedOn) if (!seen.Add(b)) return true; return false; }

Try / catch

try { style.Seal(); }
catch (InvalidOperationException ex) when (ex.Message.Contains("loop")) { log.Error("Circular BasedOn", ex); }

Prevention

When it happens

Trigger: Setting styleA.BasedOn = styleB and later styleB.BasedOn = styleA (directly or through a longer chain) before either is sealed; programmatically rewiring BasedOn in a loop over styles.

Common situations: Code-generated style graphs where parents are assigned in dependency order incorrectly; late mutation of BasedOn on cached styles creating a back-reference; merging dictionaries where two styles reference each other.

Related errors


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

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Style.cs:577

        /// <remarks>
        /// Classic "when did we enter the cycle" problem where we don't know
        ///  what to start remembering and what to check against.  Brute-
        ///  force approach here is to remember everything with a stack
        ///  and do a linear comparison through everything.  Since the Style
        ///  BasedOn hierarchy is not expected to be large, this should be OK.
        /// </remarks>
        private void CheckForCircularBasedOnReferences()
        {
            Stack basedOnHierarchy = new Stack(10);  // 10 because that's the default value (see MSDN) and the perf team wants us to specify something.
            Style latestBasedOn = this;

            while( latestBasedOn != null )
            {
                if( basedOnHierarchy.Contains( latestBasedOn ) )
                {
                    // Uh-oh.  We've seen this Style before.  This means
                    //  the BasedOn hierarchy contains a loop.
                    throw new InvalidOperationException(SR.Format(
                        SR.StyleBasedOnHasLoop));

                    // Debugging note: If we stop here, the basedOnHierarchy
                    //  object is still alive and we can browse through it to
                    //  see what we've explored.  (This does not apply if
                    //  somebody catches this exception and re-throws.)
                }

                // Haven't seen it, push on stack and go to next level.
                basedOnHierarchy.Push( latestBasedOn );
                latestBasedOn = latestBasedOn.BasedOn;
            }

            return;
        }

        // Iterates through the setters collection and adds the EventSetter information into
        // an EventHandlersStore for easy and fast retrieval during event routing. Also adds

View on GitHub (pinned to 81131a70a4)