stride3d/stride · error · ArgumentException

Class type is expected.

Error message

Class type is expected.

What it means

PropertyContainer.AddAccessorProperty registers a PropertyKey as an accessor property for a type, and the registry is keyed by Type with the assumption it maps to class instances. Passing a struct, enum, interface, or other non-class Type throws ArgumentException because accessor semantics only apply to class types.

Solutions

  1. Pass the concrete class type that owns the property.
  2. Constrain generic helper methods with `where T : class`.
  3. If the type is a struct by design, do not use accessor-property registration for it.

Example fix

// before
PropertyContainer.AddAccessorProperty(typeof(MyPoint), PositionKey); // struct
// after
PropertyContainer.AddAccessorProperty(typeof(MyEntity), PositionKey); // class
Defensive patterns

Strategy: type-guard

Validate before calling

if (!type.GetTypeInfo().IsClass)
    throw new InvalidOperationException("AddAccessorProperty requires a class type");

Type guard

static bool IsClass(Type t) => t.GetTypeInfo().IsClass;

Try / catch

try { PropertyContainer.AddAccessorProperty(type, key); }
catch (ArgumentException ex) when (ex.Message == "Class type is expected.")
{
    // caller passed a struct/interface; skip or resolve the concrete class
}

Prevention

When it happens

Trigger: Calling PropertyContainer.AddAccessorProperty(typeof(SomeStruct) or an interface, somePropertyKey) where the type's TypeInfo.IsClass is false.

Common situations: Registering accessor properties for value-type models or interfaces; generic helper code that forwards typeof(T) where T is unconstrained and instantiated with a struct.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


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

Appendix: source

Thrown at sources/core/Stride.Core/PropertyContainer.cs:527

                PropertyUpdated?.Invoke(ref this, propertyKey, tagValue, previousValue);
                propertyKey.PropertyUpdateCallback?.Invoke(ref this, propertyKey, tagValue, previousValue);
            }
        }
        else
        {
            if (tryToAdd)
                properties.Add(propertyKey, valueToSet);
            else
                properties[propertyKey] = valueToSet;
        }

        propertyKey.ObjectInvalidationMetadata?.Invalidate(Owner, propertyKey, oldValue);
    }

    public static void AddAccessorProperty(Type type, PropertyKey propertyKey)
    {
        if (!type.GetTypeInfo().IsClass)
            throw new ArgumentException("Class type is expected.", nameof(type));

        if (propertyKey.AccessorMetadata == null)
            throw new ArgumentException("Given PropertyKey doesn't have accessor metadata.", nameof(propertyKey));

        if (!AccessorProperties.TryGetValue(type, out var typeAccessorProperties))
            AccessorProperties.Add(type, typeAccessorProperties = []);

        typeAccessorProperties.Add(propertyKey);
    }

    internal void RaisePropertyContainerUpdated(PropertyKey propertyKey, object newValue, object oldValue)
    {
        PropertyUpdated?.Invoke(ref this, propertyKey, newValue, oldValue);
    }

    private object? GetNonRecursive(PropertyKey propertyKey)
    {
        // Get bound value, if any.

View on GitHub (pinned to 96fad776d2)