louthy/language-ext · error · InvalidOperationException

InvalidOperationException

Error message

InvalidOperationException

What it means

OrdResolver.MakeComparer resolves the Ord<A> implementation's Compare method via reflection and wraps it in CompareFunc. That delegate throws InvalidOperationException if the Invoke result is null where an int was required, meaning the resolved Compare member doesn't conform to static int Compare(A,A).

Solutions

  1. Fix Compare to be static, take (A, A), and return a non-null int.
  2. Remove the broken Ord implementation so OrdResolver.MakeDefault (Comparer<A>.Default) applies.
  3. Add a unit test that exercises comparison through the trait before shipping.
  4. Audit all static members named 'Compare' on the type for accidental overload collisions.

Example fix

// before
public static int? Compare(A x, A y) => null;
// after
public static int Compare(A x, A y) => Comparer<A>.Default.Compare(x, y);
Defensive patterns

Strategy: validation

Validate before calling

var m = typeof(OrdImpl).GetMethod("Compare", new[] { typeof(A), typeof(A) });
bool ok = m is { IsStatic: true } && m.ReturnType == typeof(int);

Type guard

bool IsValidCompare<A>(MethodInfo? m) => m is { IsStatic: true } && m.ReturnType == typeof(int) && m.GetParameters().Length == 2;

Try / catch

try { ordered = items.OrderBy(x => x, Ord<A>.Default); }
catch (InvalidOperationException) { ordered = items.OrderBy(x => x, Comparer<A>.Default); }

Prevention

When it happens

Trigger: Using Ord<A>-based ordering (sort, min/max, comparison APIs) on a type whose Ord implementation's Compare(A,A) returns null (wrong return type or null-producing body) when the delegate is invoked.

Common situations: Hand-rolled Ord instances with nullable int returns; refactors that changed Compare's signature; reflection-based resolution picking a wrong overload; trimming removing the real method.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of louthy/language-ext@2f0e362824 (2026-09-15). Data as JSON: /api/errors/8b9ffd61d40431e3. Report an issue: GitHub.

Appendix: source

Thrown at LanguageExt.Core/Traits/Resolve/OrdResolver.cs:77

        {
            ResolutionError = $"Trait implementation not found for: {source.Name}";
            MakeDefault();
            return;
        }
        
        // Compare
        
        var m = Resolver.Method(impl, "Compare", source, source);
        if (m is null)
        {
            ResolutionError = $"`Compare` method not found for: {source.Name}";
            MakeDefault();
            return;
        }

        CompareMethod    = m;
        CompareMethodPtr = m.MethodHandle.GetFunctionPointer();
        CompareFunc      = (x, y) => (int?)CompareMethod.Invoke(null, [x, y]) ?? throw new InvalidOperationException();

        // Equals
        
        m = Resolver.Method(impl, "Equals", source, source);
        if (m is null)
        {
            ResolutionError = $"`Equals` method not found for: {source.Name}";
            MakeDefault();
            return;
        }

        EqualsMethod    = m;
        EqualsMethodPtr = m.MethodHandle.GetFunctionPointer();
        EqualsFunc      = (x, y) => (bool?)EqualsMethod.Invoke(null, [x, y]) ?? throw new InvalidOperationException();
        
        // GetHashCode
        
        m = Resolver.Method(impl, "GetHashCode", source);

View on GitHub (pinned to 2f0e362824)