{"record":{"id":"3f7e2369986bb2cd","repo":"Humanizr/Humanizer","slug":"custom-strategy-type-gettype-fullname-must-e","errorCode":null,"errorMessage":"Custom strategy type '{GetType().FullName}' must explicitly implement IGrammaticalCaseTimeSpanHumanizeStrategy to support grammatical-case-aware durations.","messagePattern":"Custom strategy type '(.+?)' must explicitly implement IGrammaticalCaseTimeSpanHumanizeStrategy to support grammatical-case-aware durations\\.","errorType":"exception","errorClass":"NotSupportedException","httpStatus":null,"severity":"error","filePath":"src/Humanizer/TimeSpanHumanizeStrategy/DefaultTimeSpanHumanizeStrategy.cs","lineNumber":77,"sourceCode":"            collectionSeparator,\n            maxFractionalDigits,\n            roundingMode,\n            toSymbols);\n\n    string IGrammaticalCaseTimeSpanHumanizeStrategy.Humanize(\n        TimeSpan timeSpan,\n        int precision,\n        bool countEmptyUnits,\n        CultureInfo? culture,\n        TimeUnit maxUnit,\n        TimeUnit minUnit,\n        string? collectionSeparator,\n        bool toSymbols,\n        GrammaticalCase grammaticalCase)\n    {\n        if (GetType().Assembly != typeof(DefaultTimeSpanHumanizeStrategy).Assembly)\n        {\n            throw new NotSupportedException(\n                $\"Custom strategy type '{GetType().FullName}' must explicitly implement {nameof(IGrammaticalCaseTimeSpanHumanizeStrategy)} to support grammatical-case-aware durations.\");\n        }\n\n        return TimeSpanHumanizeExtensions.DefaultHumanizeWithCase(\n            timeSpan,\n            precision,\n            countEmptyUnits,\n            culture,\n            maxUnit,\n            minUnit,\n            collectionSeparator,\n            toSymbols,\n            grammaticalCase);\n    }\n}","sourceCodeStart":59,"sourceCodeEnd":92,"githubUrl":"https://github.com/Humanizr/Humanizer/blob/ffc2b77c0f30d2fb176875841424379319d0ae9b/src/Humanizer/TimeSpanHumanizeStrategy/DefaultTimeSpanHumanizeStrategy.cs#L59-L92","documentation":"Thrown by DefaultTimeSpanHumanizeStrategy's explicit IGrammaticalCaseTimeSpanHumanizeStrategy implementation when the runtime type is a subclass from a different assembly than Humanizer itself. This prevents a derived class from silently inheriting case support without owning the implementation, which could produce incorrect output.","triggerScenarios":"Creating a class that extends DefaultTimeSpanHumanizeStrategy in your own assembly, registering it via Configurator.TimeSpanHumanizeStrategy, and calling HumanizeWithCase. The assembly-equality check at line 75 detects the subclass.","commonSituations":"Developer subclasses DefaultTimeSpanHumanizeStrategy to add logging or caching and then calls case-aware APIs. The subclass inherits the explicit interface implementation, but Humanizer blocks this for safety.","solutions":["Implement IGrammaticalCaseTimeSpanHumanizeStrategy explicitly on your subclass instead of inheriting it.","Compose (wrap) a DefaultTimeSpanHumanizeStrategy rather than subclassing it.","Use the built-in DefaultTimeSpanHumanizeStrategy directly."],"exampleFix":"// before\npublic class LoggingStrategy : DefaultTimeSpanHumanizeStrategy { /* inherits case support */ }\nConfigurator.TimeSpanHumanizeStrategy = new LoggingStrategy();\n\n// after\npublic class LoggingStrategy : DefaultTimeSpanHumanizeStrategy, IGrammaticalCaseTimeSpanHumanizeStrategy\n{\n    string IGrammaticalCaseTimeSpanHumanizeStrategy.Humanize(\n        TimeSpan ts, int p, bool c, CultureInfo? cult,\n        TimeUnit max, TimeUnit min, string? sep, bool sym, GrammaticalCase gc)\n    {\n        // log here\n        return TimeSpanHumanizeExtensions.DefaultHumanizeWithCase(\n            ts, p, c, cult, max, min, sep, sym, gc);\n    }\n}","handlingStrategy":"type-guard","validationCode":"static bool IsSafeSubclass(Type strategyType) =>\n    strategyType == typeof(DefaultTimeSpanHumanizeStrategy) ||\n    strategyType.GetInterfaces().Contains(typeof(IGrammaticalCaseTimeSpanHumanizeStrategy)) &&\n    strategyType.Assembly != typeof(DefaultTimeSpanHumanizeStrategy).Assembly;","typeGuard":"static bool StrategyOwnsCaseImpl(ITimeSpanHumanizeStrategy strategy) =>\n    strategy.GetType() == typeof(DefaultTimeSpanHumanizeStrategy) ||\n    strategy is IGrammaticalCaseTimeSpanHumanizeStrategy &&\n    strategy.GetType().GetInterfaces().Contains(typeof(IGrammaticalCaseTimeSpanHumanizeStrategy));","tryCatchPattern":null,"preventionTips":["Prefer composition over inheritance for custom strategies.","If subclassing, re-implement IGrammaticalCaseTimeSpanHumanizeStrategy explicitly.","Test case-aware humanize after subclassing DefaultTimeSpanHumanizeStrategy."],"tags":["timespan","grammatical-case","custom-provider","inheritance","not-supported"],"backgroundTag":null,"analyzedSha":"ffc2b77c0f30d2fb176875841424379319d0ae9b","analyzedAt":"2026-08-13T21:42:34.584Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}