stride3d/stride · error · InvalidOperationException
Event handlers can't be added or removed after the…
Error message
Event handlers can't be added or removed after the serializer has been initialized.
What it means
AssetYamlSerializer wraps an underlying YamlSerializer that is created lazily; once it exists (serializer != null), the PrepareMembers event can no longer be subscribed. The add accessor throws InvalidOperationException so late subscribers cannot silently miss callbacks that already ran during initialization.
Solutions
- Subscribe to PrepareMembers immediately after constructing AssetYamlSerializer, before any serialize/deserialize call
- Create a fresh AssetYamlSerializer instance for code that needs its own PrepareMembers handler
- Hoist subscription to application startup before any asset I/O
- Refactor to pass member-preparation behavior via serializer settings instead of the event
Example fix
// before var assets = serializer.Deserialize(stream); // initializes serializer serializer.PrepareMembers += MyHandler; // throws // after serializer.PrepareMembers += MyHandler; // subscribe first var assets = serializer.Deserialize(stream);
Defensive patterns
Strategy: validation
Validate before calling
// subscribe only if the serializer has never been used
if (!serializerUsed)
serializer.PrepareMembers += MyHandler; Try / catch
try { serializer.PrepareMembers += MyHandler; }
catch (InvalidOperationException)
{
serializer = new AssetYamlSerializer();
serializer.PrepareMembers += MyHandler;
} Prevention
- Subscribe to PrepareMembers at construction time, before any Serialize/Deserialize call
- Avoid sharing one AssetYamlSerializer singleton across modules that need distinct handlers
- Track whether the serializer has been used and assert before late subscription
- Move member customization into startup composition code
When it happens
Trigger: Subscribing serializer.PrepareMembers += handler after any Serialize/Deserialize/GetSerializerSettings call has initialized the inner serializer; using a shared/singleton serializer instance whose first use happened elsewhere; subscribing from a different subsystem after app startup.
Common situations: Editor plugins that hook serialization after the editor already loaded assets; tests where one test serialized with the shared serializer and a later test tries to attach a handler; DI singletons shared across modules.
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
- RoutingSerializer expected in the chain of serializers
- The type of collection does not have a parameterless…
- The given container does not match the expected type.
- The type of dictionary does not have a parameterless…
- The order of the Asset.Id property must be lower than the…
AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14).
Data as JSON: /api/errors/68a565333b868a2b.
Report an issue: GitHub.
Appendix: source
Thrown at sources/assets/Stride.Core.Assets/Yaml/AssetYamlSerializer.cs:28
namespace Stride.Core.Yaml;
/// <summary>
/// Default Yaml serializer used to serialize assets by default.
/// </summary>
public class AssetYamlSerializer : YamlSerializerBase
{
private event Action<ObjectDescriptor, List<IMemberDescriptor>>? PrepareMembersEvent;
private Serializer? serializer;
public static AssetYamlSerializer Default { get; set; } = new AssetYamlSerializer();
public event Action<ObjectDescriptor, List<IMemberDescriptor>> PrepareMembers
{
add
{
if (serializer != null)
throw new InvalidOperationException("Event handlers can't be added or removed after the serializer has been initialized.");
PrepareMembersEvent += value;
}
remove
{
if (serializer != null)
throw new InvalidOperationException("Event handlers can't be added or removed after the serializer has been initialized.");
PrepareMembersEvent -= value;
}
}
/// <summary>
/// Deserializes an object from the specified stream (expecting a YAML string).
/// </summary>
/// <param name="stream">A YAML string from a stream.</param>
/// <param name="expectedType">The expected type.</param>
/// <param name="contextSettings">The context settings.</param>
/// <returns>An instance of the YAML data.</returns>View on GitHub (pinned to 96fad776d2)