dotnet/reactive · error · InvalidOperationException

An event should either have add and remove methods that…

Error message

An event should either have add and remove methods that return void or an add method that returns a type compatible with the parameter type of the remove method.

What it means

For WinRT-style events the add accessor returns a token (EventRegistrationToken) that must match the type of the remove accessor's single parameter. GetEventMethods throws InvalidOperationException when addMethod.ReturnType is non-void but is not identical to the remove method's parameter type — the token plumbing cannot be wired up.

Solutions

  1. Make the accessors conform: either both void (classic CLR event) or add returns exactly the type remove accepts (WinRT pattern)
  2. If you control the type, change the remove parameter type to match the add return type
  3. Use FromEvent with explicit add/remove lambdas to bypass name-based reflection when you can manage the token yourself

Example fix

// before
public event Token Tick { add { ...; return Token.Create(); } remove(Guid g) { ...; } } // add returns Token, remove takes Guid: mismatch
// after
public event Token Tick { add { ...; return Token.Create(); } remove(Token t) { ...; } } // types now match
Defensive patterns

Strategy: validation

Validate before calling

var ev = type.GetEvent(eventName);
var add = ev?.GetAddMethod(); var remove = ev?.GetRemoveMethod();
if (add != null && add.ReturnType != typeof(void) && remove != null &&
    remove.GetParameters().Length == 1 &&
    remove.GetParameters()[0].ParameterType != add.ReturnType)
  throw new NotSupportedException($"Event '{eventName}' token types mismatch between add and remove accessors.");

Type guard

static bool HasCompatibleTokenTypes(Type t, string name) {
  var ev = t.GetEvent(name); var add = ev?.GetAddMethod(); var remove = ev?.GetRemoveMethod();
  if (add is null || remove is null) return false;
  if (add.ReturnType == typeof(void)) return remove.GetParameters().Length == 1;
  return remove.GetParameters().Length == 1 && remove.GetParameters()[0].ParameterType == add.ReturnType;
}

Prevention

When it happens

Trigger: Reflecting over an event whose add method returns some type T but whose remove method takes a parameter of a different type; typical of non-standard custom event patterns or mismatched hand-written accessors.

Common situations: Custom token-based event systems where add returns an opaque handle of one type and remove expects another (e.g. Guid vs EventRegistrationToken), hand-rolled interop events, events re-declared with altered signatures across library versions.

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 dotnet/reactive@94b5d5ab91 (2026-09-15). Data as JSON: /api/errors/aff0ef20bdc2ed52. Report an issue: GitHub.

Appendix: source

Thrown at AsyncRx.NET/System.Reactive.Async/Linq/Operators/FromEventPattern.cs:303

                throw new InvalidOperationException("Missing remove method on event.");

            var psa = addMethod.GetParameters();
            if (psa.Length != 1)
                throw new InvalidOperationException("The add method of an event should take one parameter.");

            var psr = removeMethod.GetParameters();
            if (psr.Length != 1)
                throw new InvalidOperationException("The remove method of an event should take one parameter.");

            var isWinRT = false;

            if (addMethod.ReturnType != typeof(void))
            {
                isWinRT = true;

                var pet = psr[0];
                if (pet.ParameterType != addMethod.ReturnType)
                    throw new InvalidOperationException("An event should either have add and remove methods that return void or an add method that returns a type compatible with the parameter type of the remove method.");
            }

            var delegateType = psa[0].ParameterType;

            var invokeMethod = delegateType.GetMethod("Invoke");

            var parameters = invokeMethod.GetParameters();

            if (parameters.Length != 2)
                throw new InvalidOperationException("The delegate type for an event conforming to the traditional event pattern should take two parameters.");

            if (!typeof(TSender).IsAssignableFrom(parameters[0].ParameterType))
                throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "The sender parameter of the event is not assignable to '{0}'.", typeof(TSender).FullName));

            if (!typeof(TEventArgs).IsAssignableFrom(parameters[1].ParameterType))
                throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "The event arguments parameter of the event is not assignable to '{0}'.", typeof(TEventArgs).FullName));

            if (invokeMethod.ReturnType != typeof(void))

View on GitHub (pinned to 94b5d5ab91)