dotnet/reactive · error · InvalidOperationException
The delegate type for an event conforming to the traditional
Error message
The delegate type for an event conforming to the traditional event pattern should take two parameters.
What it means
FromEventPattern validates that the delegate type used to bridge a .NET event to an async observable follows the traditional (sender, eventArgs) two-parameter pattern. GetEventMethods reflects over the delegate's Invoke method and throws this InvalidOperationException when the parameter count differs from 2, because the adapter cannot map the event payload onto TSender/TEventArgs otherwise.
Solutions
- Use a delegate type with exactly two parameters: (object? sender, TEventArgs e), e.g. EventHandler<TEventArgs> or a matching custom delegate
- If the event has a non-standard signature, use the overload taking explicit add/remove handler functions (Action<EventHandler<TEventArgs>> add, ...) instead of reflection-based event lookup
- Verify the target event's delegate type with typeof(EventDelegate).GetMethod("Invoke").GetParameters().Length before calling
Example fix
// before AsyncObservable.FromEventPattern<MyEventArgs>(e => src.CustomEvent += e, e => src.CustomEvent -= e); // delegate takes 1 param // after AsyncObservable.FromEventPattern<object, MyEventArgs>(h => (s, e) => h(s, e), e => src.MyEvent += e, e => src.MyEvent -= e);
Defensive patterns
Strategy: validation
Validate before calling
var invoke = typeof(MyDelegate).GetMethod("Invoke");
if (invoke.GetParameters().Length != 2)
throw new InvalidOperationException("Event delegate must take (sender, args)"); Type guard
static bool IsTraditionalEventDelegate<TSender, TEventArgs>(Type d) where TSender : class
=> d.GetMethod("Invoke") is { } m
&& m.GetParameters().Length == 2
&& typeof(TSender).IsAssignableFrom(m.GetParameters()[0].ParameterType)
&& typeof(TEventArgs).IsAssignableFrom(m.GetParameters()[1].ParameterType)
&& m.ReturnType == typeof(void); Try / catch
try
{
var obs = AsyncObservable.FromEventPattern<TSender, TEventArgs>(add, remove);
}
catch (InvalidOperationException ex) when (ex.Message.Contains("two parameters"))
{
// fall back to FromEvent-style add/remove handler overload
} Prevention
- Prefer events whose delegates follow (object? sender, TEventArgs e)
- For non-standard events, use add/remove-handler overloads instead of reflection-based FromEventPattern
- Sanity-check the delegate's Invoke signature in unit tests
When it happens
Trigger: Calling FromEventPattern (or the GetEventMethods helper) with a delegate type whose Invoke method takes 0, 1, or 3+ parameters — e.g. Action (no args), EventHandler that was replaced by a single-argument 'e => {}' lambda-style handler, or a custom delegate with extra parameters.
Common situations: Migrating from classic Rx FromEventPattern to AsyncRx where events use Action-style or custom delegates; picking the wrong overload that infers a non-standard delegate type; modern events that abandon the (object sender, EventArgs e) convention.
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
- The return type of an event delegate should be void.
- Strings_Linq.EVENT_REMOVE_METHOD_SHOULD_TAKE_ONE_PARAMETER
- Strings_Linq.EVENT_WINRT_REMOVE_METHOD_SHOULD_TAKE_ERT
- Strings_Linq.EVENT_PATTERN_REQUIRES_TWO_PARAMETERS
- Value cannot be null. (Parameter 'onCompletedAsync')
AI-assisted analysis of dotnet/reactive@94b5d5ab91 (2026-09-15).
Data as JSON: /api/errors/bed968d2beb8b1b2.
Report an issue: GitHub.
Appendix: source
Thrown at AsyncRx.NET/System.Reactive.Async/Linq/Operators/FromEventPattern.cs:313
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))
throw new InvalidOperationException("The return type of an event delegate should be void.");
return (addMethod, removeMethod, delegateType, isWinRT);
}
public static EventInfo GetEventEx(this Type type, string name, bool isStatic)
{
return type.GetEvent(name, isStatic ? BindingFlags.Public | BindingFlags.Static : BindingFlags.Public | BindingFlags.Instance);
}
}View on GitHub (pinned to 94b5d5ab91)