stride3d/stride · error · ArgumentException

Expecting a type inheriting from System.Collections.ISet

Error message

Expecting a type inheriting from System.Collections.ISet<T>

What it means

SetDescriptor requires its type parameter to implement System.Collections.Generic.ISet<T>; if IsSet(type) fails it throws ArgumentException 'Expecting a type inheriting from System.Collections.ISet<T>' with nameof(type) as the parameter name. Set descriptors rely on ISet<T>'s Add/Remove semantics to build edit delegates.

Solutions

  1. Ensure the type implements ISet<T> (HashSet<T>, SortedSet<T>, or a custom ISet<T>).
  2. Use ListDescriptor/OldCollectionDescriptor instead for list-like types.
  3. Fix the factory dispatch so only ISet<T>-implementing types reach SetDescriptor.

Example fix

// before
var d = new SetDescriptor(factory, typeof(List<int>), false, convention);
// after
var d = new SetDescriptor(factory, typeof(HashSet<int>), false, convention);
Defensive patterns

Strategy: validation

Validate before calling

bool isSet = type.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ISet<>));
if (!isSet) throw new InvalidOperationException($"{type} does not implement ISet<T>; use a list descriptor");

Type guard

static bool IsSetType(Type t) => t.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ISet<>));

Try / catch

try { return new SetDescriptor(factory, type, emit, conv); }
catch (ArgumentException ex) when (ex.Message.Contains("ISet")) { return new ListDescriptor(factory, type, emit, conv); }

Prevention

When it happens

Trigger: Constructing SetDescriptor for a type that is not an ISet<T> — e.g. List<T>, IEnumerable<T>, or a custom collection without ISet<T> — directly or via a factory dispatch bug.

Common situations: Confusing HashSet<T>-backed properties with List<T> during descriptor registration; custom 'set-like' classes implementing ICollection<T> but not ISet<T>; serializer config mapping a collection property to the wrong descriptor kind.

Related errors


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

Appendix: source

Thrown at sources/core/Stride.Core.Reflection/TypeDescriptors/SetDescriptor.cs:28

public class SetDescriptor : CollectionDescriptor
{
    private static readonly List<string> ListOfMembersToRemove = ["Comparer", "Capacity"];

    private Action<object, object?> addMethod;
    private Action<object, object?> removeMethod;
    private Action<object> clearMethod;
    private Func<object, object?, bool> containsMethod;
    private Func<object, int> countMethod;
    private Func<object, bool> isReadOnlyMethod;

#pragma warning disable CS8618
    // This warning is disabled because the necessary initialization will occur 
    // in the CreateSetDelegates<T>() method, not in the constructor.
    public SetDescriptor(ITypeDescriptorFactory factory, Type type, bool emitDefaultValues, IMemberNamingConvention namingConvention)
        : base(factory, type, emitDefaultValues, namingConvention)
    {
        if (!IsSet(type))
            throw new ArgumentException("Expecting a type inheriting from System.Collections.ISet<T>", nameof(type));

        HasAdd = true;
        HasRemove = true;
        HasIndexerAccessors = true;
        HasInsert = false;
        HasRemoveAt = false;

        // extract Key, Value types from ISet<??>
        var interfaceType = type.GetInterface(typeof(ISet<>))!;
        var valueType = interfaceType.GetGenericArguments()[0];

        // if the type has late bound generics, no delegates can be created as the type is invalid for calling collection operations
        if (type.ContainsGenericParameters)
            return;

        var descriptorType = typeof(SetDescriptor).GetMethod(nameof(CreateSetDelegates), BindingFlags.NonPublic | BindingFlags.Instance)!.MakeGenericMethod([valueType]);
        descriptorType.Invoke(this, []);
    }

View on GitHub (pinned to 96fad776d2)