stride3d/stride · error · InvalidOperationException

Cannot deserialize collection to readonly collection type

Error message

Cannot deserialize collection to readonly collection type [{thisObject.GetType()}].

What it means

CollectionSerializer clears the target collection and re-adds deserialized items; if the collection instance reports itself read-only (CollectionDescriptor.IsReadOnly), items cannot be replaced, so this InvalidOperationException is thrown before reading items.

Solutions

  1. Change the member type to a writable collection (List<T>, T[], HashSet<T>, Dictionary<K,V>)
  2. Initialize the member to a new mutable instance instead of a read-only wrapper
  3. Add a DataContract/MemberWidth renaming or custom serializer for read-only collections
  4. If the type is fixed, implement a custom serializer registered in the factory

Example fix

// before
public IReadOnlyList<int> Items { get; } = Array.AsReadOnly(new int[0]);
// after
public List<int> Items { get; set; } = new List<int>();
Defensive patterns

Strategy: validation

Validate before calling

var descriptor = TypeDescriptorFactory.Current.FindDescriptor(memberType) as CollectionDescriptor;
if (descriptor?.IsReadOnly(instance) == true)
    throw new InvalidOperationException($"{memberType} is read-only; use a writable collection for deserialization");

Type guard

static bool IsWritableCollection<T>(T c) where T: System.Collections.ICollection => !c.IsReadOnly;

Try / catch

try
{
    serializer.ReadYaml(ref objectContext);
}
catch (InvalidOperationException ex) when (ex.Message.Contains("readonly collection"))
{
    // re-initialize the member with a mutable instance and retry
}

Prevention

When it happens

Trigger: Deserializing YAML into a collection instance that is read-only — e.g. property typed as IReadOnlyList<T>/IReadOnlyCollection<T>, an array-backed fixed collection, or a collection exposing IsReadOnly == true (Collection<T>.IsReadOnly, frozen collections).

Common situations: Using read-only collection interfaces as member types in YAML-serialized models; passing Array.AsReadOnly()/FrozenSet results into serialized object graphs; API changes where a property type became read-only.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14). Data as JSON: /api/errors/bcd64c01e2ad3bec. Report an issue: GitHub.

Appendix: source

Thrown at sources/core/Stride.Core.Yaml/Serialization/Serializers/CollectionSerializer.cs:148

        /// <summary>
        /// Reads the collection items.
        /// </summary>
        /// <param name="objectContext">The object context.</param>
        /// <exception cref="System.InvalidOperationException">Cannot deserialize list to type [{0}]. No Add method found.DoFormat(thisObject.GetType())
        /// or
        /// Cannot deserialize list to readonly collection type [{0}]..DoFormat(thisObject.GetType())</exception>
        protected virtual void ReadCollectionItems(ref ObjectContext objectContext)
        {
            var collectionDescriptor = (CollectionDescriptor) objectContext.Descriptor;
            var thisObject = objectContext.Instance;

            if (!collectionDescriptor.HasAdd)
            {
                throw new InvalidOperationException($"Cannot deserialize collection to type [{thisObject.GetType()}]. No Add method found");
            }
            if (collectionDescriptor.IsReadOnly(thisObject))
            {
                throw new InvalidOperationException($"Cannot deserialize collection to readonly collection type [{thisObject.GetType()}].");
            }

            // The collection may contain some initial data from its containing instance's ctor,
            // let's replace the existing data with the data we have serialized
            collectionDescriptor.Clear(thisObject);

            var reader = objectContext.Reader;

            var elementType = collectionDescriptor.ElementType;
            var index = 0;
            while (!reader.Accept<SequenceEnd>())
            {
                var currentDepth = objectContext.Reader.CurrentDepth;
                var startParsingEvent = objectContext.Reader.Parser.Current;

                try
                {
                    ReadAddCollectionItem(ref objectContext, elementType, collectionDescriptor, thisObject, index);

View on GitHub (pinned to 96fad776d2)