Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support free-form delegate signature #38

Open
thomaslevesque opened this issue Oct 1, 2019 · 6 comments
Open

Support free-form delegate signature #38

thomaslevesque opened this issue Oct 1, 2019 · 6 comments

Comments

@thomaslevesque
Copy link
Owner

As opposed to being limited to EventHandler<TEventArgs>

@Matt90hz
Copy link

Hi @thomaslevesque ,
a while ago I actually implemented a library that let you create free-form weak events (up to three parameters).

https://github.com/Matt90hz/WeakEventHandling

This is how the open delegate is created. I think that is compatible with .netstandard1.3 (not sure 100%, but it should).

public static WeakDelegate<TOwner, TParam1, TParam2> CreateOpenDelegate<TEventHandler, TOwner, TParam1, TParam2>(this TEventHandler eventHandler)
    where TEventHandler : Delegate
    where TOwner : class
{
    try
    {
        return (WeakDelegate<TOwner, TParam1, TParam2>)Delegate.CreateDelegate(typeof(WeakDelegate<TOwner, TParam1, TParam2>), null, eventHandler.Method);
    }
    catch (Exception e)
    {
        throw new ArgumentException($"The event handler must be a void method with 2 parameters of type {typeof(TParam1).Name} and {typeof(TParam2).Name}.", nameof(eventHandler), e);
    }
}

I think it wouldn't be hard to adapt my code into yours. If you are interested in the idea I might give it a try in my spare time.

@thomaslevesque
Copy link
Owner Author

thomaslevesque commented Jan 23, 2024

Hi @Matt90hz,

Thanks for the pointer!

Actually, what I had in mind was something a bit more free-form than that. What you're suggesting isn't really free-form: for instance, the delegate can't have a non-void return type, out or ref parameters, etc. What I'd like to achieve is a way to really specify any delegate you like. However I never found the time to really look into it...

TBH, I think the need for this is quite limited. The (object sender, TEventArgs args) pattern is a widespread convention, it's pretty rare to encounter events that don't follow it. And if you're using this library, it means you're the publisher of the event, so you can just follow that pattern as well.

The approach used in the library requires knowing the delegate's shape, mostly for performance reasons. Supporting free-form delegated would require a lot of reflection and runtime code generation, which would negatively impact performance. So I don't think I will ever implement this...

@thomaslevesque
Copy link
Owner Author

runtime code generation

Mentioning this just made me think that we now have compile-time code generators in .NET, so maybe it would be possible to leverage that...

@Matt90hz
Copy link

Oh I see no restrictions what so ever. It would be pretty cool to implement, even if relatively useful as you pointed out.

With code generators something like this can be definitely done:

public partial class  MyClass
{
    private readonly FormFreeWeakEventSource<EventHandler<EventArgs>> _myEvent = new();
}

//Generated code
partial class MyClass
{
    public event EventHandler<EventArgs> MyEvent
    {
        add
        {
            _myEvent.Subscribe(value);
        }
        remove
        {
            _myEvent.Unsubscribe(value);
        }
    }
}

But at this point even an analyzer might do the trick. After all we just want to enforce the same delegate signature on the WeakEventSource and the event.

Been able to do this would be the real game changer imho:

[WeakEvent]
public event AnyEventHandler MyEvent;

But I don't know how to get there. Maybe with interceptors, if one day they will support more than just methods.

I don't know maybe there is a better way. But I might try to code generate the weak event just for fun.

@thomaslevesque
Copy link
Owner Author

Actually, the problematic part isn't really the event declaration with the add/remove accessors (although generating this would certainly improve the experience).

The hard part is the open-instance delegate (see README). If I know the shape of the delegate statically, I can just declare the open-instance delegate explicitly in the code. But for a free-form delegate, I can't do that. Maybe I could generate the appropriate delegate type dynamically (which isn't trivial), but then I would have another problem: how to call it if I don't know its signature statically?

It might be possible to generate the appropriate code based on usage; that's what I meant when I mentioned generators.

Been able to do this would be the real game changer imho:

[WeakEvent]
public event AnyEventHandler MyEvent;

Yeah... unfortunately, for this to work, we would need to declare the event as partial, so that the generator can provide the implementation, but the partial modifier is not allowed for events. I haven't really looked at interceptors yet, tbh.

@Matt90hz
Copy link

You are right.

I think that if there is some sort of marker for the code generation everything can be created dynamically. Given that the consumer of the library tolerates some boilerplate code.

Something like this:

// WeakFreeFormEventSource would be an empty class used just as a marker
partial class  MyWeakEventSource : WeakFreeFormEventSource<WeirdHandler> { }

// Just any delegate it doesn't matter
public delegate int WeirdHandler(int a, int b);

public class Publisher
{
    readonly MyWeakEventSource _weirdEvent = new();

    public event WeirdHandler? WeirdEvent
    {
        add => _weirdEvent.Subscribe(value);
        remove => _weirdEvent.Unsubscribe(value);
    }

    void OnWeirdEvent(int a, int b)
    {
        IEnumerable<int> results = _weirdEvent.Raise(a, b);
    }

}

And the generated code would be conceptually:

// Generated
partial class MyWeakEventSource
{
    delegate int OpenDelegate(object target, int arg1, int arg2);

    readonly DelegateCollection _delegateCollection = new();

    public void Subscribe(WeirdHandler? handler) { /* logic */ }

    public void Unsubscribe(WeirdHandler? handler) { /* logic */ }

    public IEnumerable<int> Raise(int a, int b) { /* logic */ }
}

I think that this might be possible to achieve. A lot of the logic already written can also be reused.

I have seen stuff like this done in some libraries, like OneOf.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants