Search code examples
c#system.reactive

Observable.FromEvent with event args that have the in modifier cause exception


I'm using a 3rd party library which has an event\event handler, which I'd like to convert to an observable. The event handler delegate wants a struct, with the in keyword. This causes an exception:

System.ArgumentException - Cannot bind to the target method because its signature is not compatible with that of the delegate type.

from

System.Private.CoreLib.dll!System.Reflection.RuntimeMethodInfo.CreateDelegateInternal(System.Type delegateType, object firstArgument, System.DelegateBindingFlags bindingFlags) Line 556    C#
    System.Private.CoreLib.dll!System.Reflection.RuntimeMethodInfo.CreateDelegate(System.Type delegateType, object target) Line 534 C#
    System.Reactive.dll!System.Reactive.ReflectionUtils.CreateDelegate<Test.TheDelegate>(object o, System.Reflection.MethodInfo method) Line 18 C#

If I remove the in from the arguments, It works as expected. I'm at a loss to understand why? Is even possible to do?

using System;
using System.Diagnostics;
using System.Reactive.Linq;

Test test = new();

Start();
test.TriggerEvent();
test.TriggerEvent();

void Start()
{
    Debug.WriteLine("Start");
    var o = Observable.FromEvent<Test.TheDelegate, SomeStruct>
        (h => test.TheEvent += h, 
         h => test.TheEvent -= h)
                      .Subscribe(OnNext);
}

void OnNext(SomeStruct s) => Debug.WriteLine("OnNext");

public class Test
{
    public delegate void TheDelegate(in SomeStruct o);

    // Without the in (or ref) keyword, no exception
    //public delegate void TheDelegate(SomeStruct o);

    public event TheDelegate? TheEvent;

    public void TriggerEvent()
    {
        SomeStruct s = new();
        TheEvent?.Invoke(s);
        Debug.WriteLine("Event Fired");
    }
}

public struct SomeStruct
{ }

Solution

  • I was able to reproduce the ArgumentException at run-time. To solve it, you could use the Observable.FromEvent overload that has an extra conversion parameter:

    public static IObservable<TEventArgs> FromEvent<TDelegate, TEventArgs>(
        Func<Action<TEventArgs>, TDelegate> conversion,
        Action<TDelegate> addHandler,
        Action<TDelegate> removeHandler);
    

    Usage:

    var observable = Observable.FromEvent<Test.TheDelegate, SomeStruct>
    (
        action => (in SomeStruct arg) => action(arg),
        h => test.TheEvent += h,
        h => test.TheEvent -= h
    );
    var subscription = observable.Subscribe(OnNext);
    

    The conversion converts an Action<SomeStruct> to a Test.TheDelegate. Contrary to the simpler overload, the validity of the conversion is checked at compile-time.