egametang/ET · error · NotSupportedException
检测到循环引用,类型: {runtimeType.FullName}
Error message
检测到循环引用,类型: {runtimeType.FullName} What it means
Thrown by EmitterContext.Enter when the same object instance is entered twice along the current emission path. The emitter tracks visited references to detect cycles in the object graph; a re-entry means the graph loops back on itself, which would otherwise recurse infinitely while generating C# literals.
Source
Thrown at Packages/cn.etetet.config/Scripts/Model/Share/CSharpObjectCodeEmitter.cs:900
return false;
}
private string Indent(int level)
{
return new string(' ', level * 4);
}
[EnableClass]
private sealed class EmitterContext
{
private readonly HashSet<object> visited = new(new ObjectReferenceComparer());
public void Enter(object value, Type runtimeType)
{
if (!this.visited.Add(value))
{
throw new NotSupportedException($"检测到循环引用,类型: {runtimeType.FullName}");
}
}
public void Exit(object value)
{
this.visited.Remove(value);
}
}
[EnableClass]
private sealed class ObjectReferenceComparer : IEqualityComparer<object>
{
bool IEqualityComparer<object>.Equals(object x, object y)
{
return ReferenceEquals(x, y);
}
int IEqualityComparer<object>.GetHashCode(object obj)View on GitHub (pinned to 5cab01f7a8)
Solutions
- Break the cycle before emitting: set parent/back-references to null or replace them with id references.
- Project the cyclic graph into an acyclic DTO (ids instead of object pointers) for export.
- Mark back-reference fields as non-serialized so the emitter skips them.
Example fix
// before
public class TreeNode
{
public List<TreeNode> Children = new();
public TreeNode Parent; // cycle: child -> parent -> child
}
// after
public class TreeNode
{
public List<TreeNode> Children = new();
[NonSerialized] public TreeNode Parent; // skip during export
} Defensive patterns
Strategy: validation
Validate before calling
// detect cycles in the graph before emitting (BFS reference check)
static bool HasCycle(object root)
{
var visited = new HashSet<object>(new ObjectReferenceComparer());
var stack = new Stack<object>();
stack.Push(root);
while (stack.Count > 0)
{
var cur = stack.Pop();
if (cur == null) continue;
if (!visited.Add(cur)) return true;
foreach (var f in cur.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public))
if (f.GetValue(cur) is object child && !f.FieldType.IsValueType) stack.Push(child);
}
return false;
} Prevention
- Project cyclic runtime graphs into acyclic DTOs (id references) before export.
- Mark back/parent references [NonSerialized].
- Never pass linked-list/tree-with-parent structures directly to the emitter.
When it happens
Trigger: An object graph with a reference cycle: A references B and B references A; a parent/child collection where a child points back to its parent; a self-referential field (node.Next = node).
Common situations: Runtime data structures that legitimately cycle (linked lists, trees with parent pointers, bidirectional relationships) being passed to an exporter that only supports trees/DAGs.
Related errors
- 不支持导出 UnityEngine.Object 类型: {runtimeType.FullName}
- {runtimeType.FullName} 缺少可用于导出的公共构造函数。必须通过构造函数提供的成员: {names}
- 无法为类型 {type.FullName} 生成稳定排序键
- condition number parse error: {text}
- condition token error at {this.index}: {c}
AI-assisted analysis of egametang/ET@5cab01f7a8 (2026-08-13).
Data as JSON: /api/errors/4bf4a9d124eb241d.
Report an issue: GitHub.