{"record":{"id":"9f8fbce8d8e1e489","repo":"stride3d/stride","slug":"expecting-a-type-inheriting-from-system-collections-iset-t","errorCode":null,"errorMessage":"Expecting a type inheriting from System.Collections.ISet<T>","messagePattern":"Expecting a type inheriting from System\\.Collections\\.ISet<T>","errorType":"exception","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"sources/core/Stride.Core.Reflection/TypeDescriptors/SetDescriptor.cs","lineNumber":28,"sourceCode":"public class SetDescriptor : CollectionDescriptor\n{\n    private static readonly List<string> ListOfMembersToRemove = [\"Comparer\", \"Capacity\"];\n\n    private Action<object, object?> addMethod;\n    private Action<object, object?> removeMethod;\n    private Action<object> clearMethod;\n    private Func<object, object?, bool> containsMethod;\n    private Func<object, int> countMethod;\n    private Func<object, bool> isReadOnlyMethod;\n\n#pragma warning disable CS8618\n    // This warning is disabled because the necessary initialization will occur \n    // in the CreateSetDelegates<T>() method, not in the constructor.\n    public SetDescriptor(ITypeDescriptorFactory factory, Type type, bool emitDefaultValues, IMemberNamingConvention namingConvention)\n        : base(factory, type, emitDefaultValues, namingConvention)\n    {\n        if (!IsSet(type))\n            throw new ArgumentException(\"Expecting a type inheriting from System.Collections.ISet<T>\", nameof(type));\n\n        HasAdd = true;\n        HasRemove = true;\n        HasIndexerAccessors = true;\n        HasInsert = false;\n        HasRemoveAt = false;\n\n        // extract Key, Value types from ISet<??>\n        var interfaceType = type.GetInterface(typeof(ISet<>))!;\n        var valueType = interfaceType.GetGenericArguments()[0];\n\n        // if the type has late bound generics, no delegates can be created as the type is invalid for calling collection operations\n        if (type.ContainsGenericParameters)\n            return;\n\n        var descriptorType = typeof(SetDescriptor).GetMethod(nameof(CreateSetDelegates), BindingFlags.NonPublic | BindingFlags.Instance)!.MakeGenericMethod([valueType]);\n        descriptorType.Invoke(this, []);\n    }","sourceCodeStart":10,"sourceCodeEnd":46,"githubUrl":"https://github.com/stride3d/stride/blob/96fad776d210c221682aac1ccdf4c79dc046fc38/sources/core/Stride.Core.Reflection/TypeDescriptors/SetDescriptor.cs#L10-L46","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Ensure the type implements ISet<T> (HashSet<T>, SortedSet<T>, or a custom ISet<T>).","Use ListDescriptor/OldCollectionDescriptor instead for list-like types.","Fix the factory dispatch so only ISet<T>-implementing types reach SetDescriptor."],"exampleFix":"// before\nvar d = new SetDescriptor(factory, typeof(List<int>), false, convention);\n// after\nvar d = new SetDescriptor(factory, typeof(HashSet<int>), false, convention);","handlingStrategy":"validation","validationCode":"bool isSet = type.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ISet<>));\nif (!isSet) throw new InvalidOperationException($\"{type} does not implement ISet<T>; use a list descriptor\");","typeGuard":"static bool IsSetType(Type t) => t.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ISet<>));","tryCatchPattern":"try { return new SetDescriptor(factory, type, emit, conv); }\ncatch (ArgumentException ex) when (ex.Message.Contains(\"ISet\")) { return new ListDescriptor(factory, type, emit, conv); }","preventionTips":["Use HashSet<T> or SortedSet<T> for set-serialized properties.","Verify ISet<T> implementation before registering a type with SetDescriptor.","Keep custom set-like classes implementing ISet<T>, not just ICollection<T>."],"tags":["reflection","collection","iset","csharp"],"backgroundTag":"invalid-constructor-argument","analyzedSha":"96fad776d210c221682aac1ccdf4c79dc046fc38","analyzedAt":"2026-09-14T02:59:31.279Z","contentChangedAt":"2026-09-14T02:59:31.279Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}