dotnet/reactive · error · InvalidOperationException

Strings_Linq.EVENT_MUST_RETURN_VOID

Error message

Strings_Linq.EVENT_MUST_RETURN_VOID

What it means

Event delegates must return void; the .NET event pattern defines handlers with a void return. If the delegate's Invoke method returns a non-void type, Rx cannot treat it as an event handler and GetEventMethods throws InvalidOperationException.

Solutions

  1. Use a void-returning delegate (Action/EventHandler style) for the event.
  2. If the API returns values, model it as a method call or use Observable.FromAsyncPattern-style adapters instead of FromEventPattern.
  3. Wrap the value-returning callback manually with Observable.Create.

Example fix

// before
public delegate bool Validate(object sender, EventArgs e);
// after
public delegate void Validate(object sender, EventArgs e); // or EventHandler
Defensive patterns

Strategy: validation

Validate before calling

var invoke = delegateType.GetMethod("Invoke");
if (invoke?.ReturnType != typeof(void))
    throw new ArgumentException("Event delegates must return void");

Try / catch

try
{
    var src = Observable.FromEventPattern(target, eventName);
}
catch (InvalidOperationException ex) when (ex.Message.Contains("void"))
{
    // adapt the callback-style API with Observable.Create instead
}

Prevention

When it happens

Trigger: Passing a delegate type (e.g. Func<int, string, bool>) to FromEventPattern/FromEvent where the handler returns a value.

Common situations: Callback-style APIs mistakenly exposed as events; custom delegates with return values used for observer-style notifications; hand-rolled add/remove accessors accepting value-returning delegates.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of dotnet/reactive@94b5d5ab91 (2026-09-15). Data as JSON: /api/errors/1b125e1738fd3ff5. Report an issue: GitHub.

Appendix: source

Thrown at Rx.NET/Source/src/System.Reactive/Internal/ReflectionUtils.cs:92

            if (parameters.Length != 2)
            {
                throw new InvalidOperationException(Strings_Linq.EVENT_PATTERN_REQUIRES_TWO_PARAMETERS);
            }

            if (!typeof(TSender).IsAssignableFrom(parameters[0].ParameterType))
            {
                throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Strings_Linq.EVENT_SENDER_NOT_ASSIGNABLE, typeof(TSender).FullName));
            }

            if (!typeof(TEventArgs).IsAssignableFrom(parameters[1].ParameterType))
            {
                throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Strings_Linq.EVENT_ARGS_NOT_ASSIGNABLE, typeof(TEventArgs).FullName));
            }

            if (invokeMethod.ReturnType != typeof(void))
            {
                throw new InvalidOperationException(Strings_Linq.EVENT_MUST_RETURN_VOID);
            }
        }

        /// <summary>
        /// Determine whether a type represents a WinRT event registration token
        /// (https://learn.microsoft.com/en-us/uwp/api/windows.foundation.eventregistrationtoken).
        /// </summary>
        /// <param name="t">The type to check.</param>
        /// <returns>True if this represents a WinRT event registration token</returns>
        /// <remarks>
        /// <para>
        /// We used to perform a simple comparison with typeof(EventRegistrationToken), but the
        /// introduction of C#/WinRT has made this problematic. Before C#/WinRT, the .NET
        /// projection of WinRT's Windows.Foundation.EventRegistrationToken type was
        /// System.Runtime.InteropServices.WindowsRuntime.EventRegistrationToken. But that type is
        /// associated with the old WinRT interop mechanisms in which the CLR works directly with
        /// WinMD. That was how it worked up as far as .NET Core 3.1, and it's still how .NET
        /// Framework works, but this direct WinMD support was removed in .NET 5.0.

View on GitHub (pinned to 94b5d5ab91)