dotnet/wpf · error · XpsSerializationException
SR.ReachSerialization_CycleDetectedInSerialization
Error message
SR.ReachSerialization_CycleDetectedInSerialization
What it means
ReachObjectContext.CreateContext throws XpsSerializationException (ReachSerialization_CycleDetectedInSerialization) when it detects that the object about to get a new serialization context is already being serialized higher up the context stack (its TargetObject matches a current context). Serializing a cyclic object graph would loop forever or overflow the stack, so the XPS serializer fails fast. XPS documents must be tree-shaped; cycles are unsupported.
Solutions
- Break the cycle by making the graph a strict tree before serialization (remove back-references from the serialized tree)
- Clone shared sub-objects so each appears only once in the graph
- Skip serializing parent references (mark them [SerializeIgnore]-like by rebuilding a serializable copy)
- Catch XpsSerializationException and walk the graph for repeated references before saving
Example fix
// before child.Parent = parent; // cycle: parent.Children contains child writer.Write(parent); // after var copy = CloneForSerialization(parent); // drop Parent back-reference writer.Write(copy);
Defensive patterns
Strategy: validation
Validate before calling
static bool HasCycle(object root) { var seen = new HashSet<object>(); var stack = new Stack<object>(); stack.Push(root); while (stack.Count > 0) { var o = stack.Pop(); if (o == null || !seen.Add(o)) continue; foreach (var c in GetChildren(o)) stack.Push(c); } return false; } // GetChildren enumerates visual/logical children; a repeated node indicates a cycle
Type guard
static bool IsAcyclic(object root) => !ContainsReference(root, root, new HashSet<object>());
Try / catch
try { writer.Write(root); } catch (System.Windows.Xps.XpsSerializationException ex) when (ex.Message.Contains("cycle")) { log.Error("Cyclic object graph passed to XPS writer", ex); throw; } Prevention
- Never reuse the same visual instance twice in one tree
- Strip parent/back-references before serializing
- Clone shared subtrees into distinct instances
- Add a cycle-detection walk before every XPS write
When it happens
Trigger: Calling the XPS serializer on an object graph containing a reference cycle — e.g. a Visual whose Children collection (transitively) contains an ancestor, or parent/child properties pointing at each other.
Common situations: Reusing the same Visual/FixedPage instance in multiple places in a document; models with bidirectional parent-child references being fed straight into XpsDocumentWriter; shared resources attached into the visual tree more than once.
Related errors
- SR.ReachSerialization_NoSerializer
- SR.ReachSerialization_NoSerializer
- SR.ReachSerialization_NoSerializer
- SR.ReachSerialization_NoSerializer
- SR.ReachSerialization_NoSerializer
AI-assisted analysis of dotnet/wpf@81131a70a4 (2026-09-14).
Data as JSON: /api/errors/bb9710e8cfdbd82d.
Report an issue: GitHub.
Appendix: source
Thrown at src/Microsoft.DotNet.Wpf/src/ReachFramework/Serialization/manager/ReachObjectContext.cs:132
{
//
// Check for element pre-existance to avoid infinite loops
// in the process of serialization
//
int stackIndex = 0;
object currentObject = null;
for(currentObject = serializationManager.GraphContextStack[stackIndex];
currentObject != null;
currentObject = serializationManager.GraphContextStack[++stackIndex])
{
SerializableObjectContext currentObjectContext = currentObject as SerializableObjectContext;
if(currentObjectContext!=null &&
currentObjectContext.TargetObject == serializableObject)
{
throw new XpsSerializationException(SR.ReachSerialization_CycleDetectedInSerialization);
}
}
SerializableObjectContext serializableObjectContext;
lock (_stackLock)
{
serializableObjectContext =
_recycableSerializableObjectContexts.Count == 0 ?
null :
(SerializableObjectContext)_recycableSerializableObjectContexts.Pop();
}
if(serializableObjectContext == null)
{
serializableObjectContext = new SerializableObjectContext(serializableObject,
serializablePropertyContext);
}
elseView on GitHub (pinned to 81131a70a4)