dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
14.98k stars 4.66k forks source link

Provide built-in support for "weak event" pattern #18645

Open Opiumtm opened 8 years ago

Opiumtm commented 8 years ago

"Weak event" pattern is a widely used pattern. But still, there is no out-of-the-box support for it in .NET

robloo commented 2 years ago

Yes, it would break things. Honestly though, weak events should have been the default from the beginning and strong event references used opt-in (of course still supported). Every single large app ends up having to roll their own solution for weak events and it quickly becomes the default event pattern.

dotMorten commented 2 years ago

While I whole heartedly disagree, there’s really no point in even discussing it as such a breaking behavior is almost sure to be a non starter. I could come up with numerous examples where such a behavior is going to create very unpredictable results

robloo commented 2 years ago

I'll drop it as I agree with your assessment its a non-starter. I would say if things were designed differently from the start app architecture changes can avoid any such unpredictability while also avoiding common memory leaks which defeat the whole point of a managed runtime. (I also said the strong pattern could be kept opt in where its easier for things) This needed to be rethought at a fundamental level so I would encourage letting go of pre-conceived patterns. Anyway, thats all for a future language/runtime after us.

legistek commented 1 year ago

I'm glad I found this issue and that it's still open. Count me in as a vote for this.

I agree with the comment that weak events should have been the default from the beginning. (I'm also not sure how changing this now would be breaking - is there really code out there in the wild that relies on objects being kept alive solely by virtue of an event subscription? Talk about asking for trouble. But it's a moot point of course as it'll never happen.)

Certainly we all can make our own Weak Event patterns. WPF does it and that pattern can be copied easily. There are plenty of others. How efficient they are is dubious. What they indisputably are is ugly and hackish.

Being able to simply say:

obj.AnyEvent += my_handler weak;

would just be so delightful.

thomaslevesque commented 1 year ago

I agree with the comment that weak events should have been the default from the beginning. (I'm also not sure how changing this now would be breaking - is there really code out there in the wild that relies on objects being kept alive solely by virtue of an event subscription? Talk about asking for trouble. But it's a moot point of course as it'll never happen.)

I'm pretty sure it would break a lot of existing code. You might not consciously "rely on objects being kept alive solely by virtue of an event subscription", but in practice, it happens a lot, as soon as you start using lambda expressions to subscribe to events. If you do something like this:

void Subscribe(int foo)
{
    something.SomeEvent += () => Console.WriteLine(foo);
}

Pretty familiar, right? Nothing suspicious here? But the event handler captures a local variable, so the compiler actually creates a closure class with an instance field for foo, and the lambda becomes an instance method of that class. So it's translated to something like this:

void Subscribe(int foo)
{
    var closure = new Closure { foo = foo };
    something.SomeEvent += closure.Lambda;
}

class Closure // Actually named something like YourClass+<>c__DisplayClass8_0
{
    public int foo;
    public void Lambda() // Actually named something like <Subscribe>b__0
    {
        Console.WriteLine(foo);
    }
}

Once the Subscribe method returns, the closure instance is only referenced by the event publisher. So if it were a weak event, the instance would be garbage collected, and the event handled would no longer be executed. Oops!

legistek commented 1 year ago

@thomaslevesque ah that's interesting! I would've guessed the class instance (or static class as the case may be) on which Subscribe(int foo) was called would hold a reference to the Closure instance for at least its own lifespan - for exactly this reason - but if not then yes I definitely see your point.

That raises an interesting point then about a hypothetical weak keyword for event subscriptions. If I understand you right, it would basically be pointless to create a weak event subcription with lambdas because in the vast majority of cases no one would hold a strong reference to them and they'd get GC'd right away. It would only make sense - and maybe should only be allowed - for actual class methods then.

igor84 commented 1 year ago

I don't see anyone mentioning this so I am wondering if I am missing something.

If we would get the ability to do weak subscribes and we start to rely on it, from time to time it might happen that although subscriber is not referenced by anything and is "disposed" and ready to be collected by the GC that just didn't happen yet and the publisher sends the event at that time. That would mean that subscribed method would still run on the subscriber and it might not expect it if it is in some disposed state. Is that right? Seems like that would lead to random bugs that are very hard to reproduce.

axa88 commented 3 weeks ago

I don't see anyone mentioning this so I am wondering if I am missing something.

If we would get the ability to do weak subscribes and we start to rely on it, from time to time it might happen that although subscriber is not referenced by anything and is "disposed" and ready to be collected by the GC that just didn't happen yet and the publisher sends the event at that time. That would mean that subscribed method would still run on the subscriber and it might not expect it if it is in some disposed state. Is that right? Seems like that would lead to random bugs that are very hard to reproduce.

Maybe im the one missing something but how is this different from any other code marked for collection. An object without any references is marked for collection but has say the object itself contains a timer that expires and executes...??

Or you mean this?

Anyway, when we going to get this feature... Im suffering from IDisposable creep from public APIs that implement scores of static events

MatthewSteeples commented 3 weeks ago

Maybe im the one missing something but how is this different from any other code marked for collection. An object without any references is marked for collection but has say a timer that expires and executes...??

As things currently stand, being referenced by a timer would prevent the object being marked for collection.

julealgon commented 2 weeks ago

I'm a bit confused by this proposal. Is the main idea to just remove the need for manually unsubscribing from events?

Isn't it a good practice today to unsubscribe to events when done with them, potentially as part of implementing IDisposable?

Is there a situation where that best practice cannot be followed at all that would warrant these "weak events" as the only alternative?

robloo commented 2 weeks ago

@julealgon

Is the main idea to just remove the need for manually unsubscribing from events?

No. This is only for specific situations where it's needed.

The original topic is simple. Almost all frameworks and large projects end up having to roll their own solution for the weak event pattern. WPF has an implementation. Avalonia another. Several projects on GitHub do as well. I won't get into the details of why but you are welcome to dig into it.

We already know this functionality is needed from the countless examples and implementations. It should just be pulled into the core framework itself so everyone can share the same implementation.

julealgon commented 2 weeks ago

Thanks for the nudge @robloo . I found this article which explains the pattern very well based on your mention of WPF.

To be fair, ever since IObservable and Rx were introduced, I thought the native event patterns would end up being deprecated over time and the built-in IDisposable nature of an IObservable registration would take care of automatically unregistering handlers, but apparently I was wrong, otherwise WPF and other frameworks would've just gone with that.