-
Notifications
You must be signed in to change notification settings - Fork 4.7k
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
Provide built-in support for "weak event" pattern #18645
Comments
How would it look like? How would it be implemented? |
@svick closer to the original event usage, the better If we look at Without C# support it would be just static helper class like I think, |
Related proposal to make weak event handlers a language feature: dotnet/roslyn#101. |
Searching around, I found two decent existing implementations of weak event:
The implementations seem to be fairly similar, resulting in code like: private readonly WeakEventSource<MyEventArgs> _myEventSource = new WeakEventSource<MyEventArgs>();
public event EventHandler<MyEventArgs> MyEvent
{
add { _myEventSource.Subscribe(value); }
remove { _myEventSource.Unsubscribe(value); }
} I don't think you can do much better without changing the language. Maybe the corefx version of weak event could look something like this, possibly even copying code from one of the two implementations. |
@svick I know about these implementations. Major issue with all these implementations is a fundamental inability to automatically unsubscribe weak event proxies from event source. Here is example implementation of weak event pattern in T10 library public class WeakReference<TInstance, TSource, TEventArgs> where TInstance : class
{
public WeakReference<TInstance> Reference { get; protected set; }
public Action<TInstance, TSource, TEventArgs> EventAction { get; set; }
public Action<TInstance, WeakReference<TInstance, TSource, TEventArgs>> DetachAction { get; set; }
public WeakReference(TInstance instance) { Reference = new WeakReference<TInstance>(instance); }
public virtual void Handler(TSource source, TEventArgs eventArgs)
{
TInstance instance;
if (Reference != null && Reference.TryGetTarget(out instance))
{
EventAction?.Invoke(instance, source, eventArgs);
}
else
{
// Instance surely doesn't survive garbage collections, so passing null for it
// Don't removed unnecessary delegate parameter for backward compatibility
DetachAction?.Invoke(null, this);
}
}
} DetachAction is required to properly unsubscribe proxy object from event source. To avoid this, similar to |
I've been working on adding something like this... my current state is at https://gist.github.com/SamuelEnglard/7ed7a020d770fe137f7e5c19adce43b1 Using |
@SamuelEnglard sadly, it wouldn't be usable on .NET Native builds, because .NET Native doesn't support IL emit at runtime. |
@Opiumtm in theory all the emitting I'm doing could be done at compile time since I'm really just "reinventing" the stuff the compiler does for current events |
@Opiumtm What Template10 has is weak event handler (i.e. you specify whether it's weak or not when subscribing a handler to an event); the two libraries I linked to have weak events (you specify whether it's weak when declaring an event). Because of that, they don't require the user to manually specify any DetachAction, or anything like that. I assumed you wanted weak events (because that's what you called it), not weak event handlers. You might want to clarify that in your original post and the title. |
My implementation doesn't use proxies, and handlers are automatically unsubscribed. |
I've done some more work and updated the gist. Except for the fact that I'm doing emit at runtime I'm pretty sure what I've created is a "true" weak event |
Could you show how your implementation is used? All I see is a |
@thomaslevesque Good point, give me a minute and I'll upload my test TestRunner.cs is what I've been using to test with |
Actually, I misunderstood what your class was doing. I wrote the following test: https://gist.github.com/thomaslevesque/f5c86e23b145b841977896ca7aad59e1 |
Exactly. Pretty much just replace usage of the static members of |
@SamuelEnglard @thomaslevesque |
I should be able to have a speclet for this soon based on an updated version of my gist |
Wanted to give an update that I moved my work to https://github.com/SamuelEnglard/weakdelegates |
Ok, so the code in https://github.com/SamuelEnglard/weakdelegates should work. API: namespace WeakDelegates
{
// Usage is the same as System.Delegate.Combine(Delegate, Delegate);
public static T Combine<T>(T a, T b) where T : class;
// Usage is the same as System.Delegate.Remove(Delegate, Delegate);
public static T Remove<T>(T source, T value) where T : class;
} Primary issue currently is that I haven't found a way to make the syntax nice and short like we have for regular delegates. In theory that is more of a C#/compiler issue though |
I think this is an interesting idea and it might be worth pursuing. @terrajobst what do you think? |
While we're throwing things in, here's what I've had. An implementation using |
Having this in corefx would be nice, but it would be even better to have it directly in the language. The syntax could be something like this:
|
@thomaslevesque I don't think the event declaration should declare event. Instead the event handler subscription should declare it to be weak. The class that exposes the event can't possibly know if the instance that's subscribing is going to be a longer-lived object or not. That's something the subscriber knows. |
Well, if the subscriber is long-lived, it doesn't matter whether it's subscribed weakly or not, so the event publisher doesn't need to know about it anyway. |
are there any updates on this ? :) EDIT: I'm currently in the need of a cross platform weak event manager and stumbled upon this thread |
@JaggerJo my implementation targets netstandard1.1, netstandard2.0, net35, net40, and net45, so it should work on almost all current platforms |
Thank you, this might help a lot. I’m working mostly in F# so I have to test if your implementation “feels right”. |
Having the ability for a publisher to declare a weak event is far less useful than for a subscriber to have the ability to listen to an event weakly (though the former is far easier problem to solve). The reason for this is simply because there are vast numbers of existing framework classes that publish events that need be listened to weakly (think of all the events published by WPF controls for example), and those could never be changed without breaking all sorts of existing code. Here's my implementation that I'm currently using for weak event listening in some of my projects: https://github.com/davidmilligan/WeakEventListener . It's pretty simple, but it gets the job done. |
There are plenty of those. But the entire point of this issue is to have it as a language feature. |
No it’s not, or this issue is in the wrong repo. The related language feature request was rejected, and I doubt anything will ever come of it in the future (don’t get me wrong, I would love to see such a feature). However, that doesn’t preclude framework support for it, which, while it wouldn’t be nearly as nice and succinct as a language feature, would at least standardize an API for doing this sort of thing and eliminate the need for the proliferation of all these different implementations. Therefore promoting reusability and interoperability of code.
Of course they do, but that’s not the point. When a singular tiny object is kept alive instead of a tree of UI objects in the 100s of MB -> you have succeeded. |
@davidmilligan: Yes, WPF UI elements can be a pain. I never really understood why WPF decided to deny using the dispose pattern. A
You are right, still, I came up workarounds like this to make UI elements "disposable" (this specific solution works only for elements added to a
Of course. But I didn't talk about already existing components, which don't use weak events either. For them you need to remove their event registrations explicitly, that's why one needs the aforementioned workarounds (in WPF, at least) where you can do such cleanups. But for newly designed components, where you can decide whether to use weak events or remove the handlers in |
From https://docs.microsoft.com/en-us/dotnet/api/system.idisposable?view=netframework-4.8 :
|
@dotMorten: Even an empty WPF Secondly, the dispose pattern is definitely not just for unmanaged resources. From https://docs.microsoft.com/en-us/visualstudio/code-quality/ca1063?view=vs-2019#pseudo-code-example:
where those other methods are |
@koszeggy Your example is showing disposing other managed resources that has unmanaged resources, so my argument still stands (yes you should often be disposing IDisposable objects when it's not coming from the finalizer, but in the end it's about releasing the native resources) |
@dotMorten: Well, not quite.
And maybe it's just my taste but I consider potentially non auto-reclaimable big objects scarce resources. Btw, I like the approach of streams: similarly to WPF, neither the base But before being too offtopic let's return to weak events. As I said I can accept if someone prefers them. And I understand that they can be tempting in environments such as WPF, which didn't make releasing resources in a deterministic way easy. Though workarounds like this can be complicated, I still think using weak events is just choosing an even worse solution. My main concern about weak references that they just bring in more unmanaged resources to solve the problem. Even worse, Final words: again, I accept if someone prefers using an architecture built up around weak events. You can find very nice weak event manager solutions everywhere on the net and that's fine. I just wouldn't be happy to see that they are encouraged to be used everywhere by getting language support. |
Streams are a great example of use of native resources (sockets, file handles etc). It's more the exception than the rule that a stream is purely managed (And those that are purely managed doesn't really clean up large resources because they rely on finalizers to do that) Anyway yes back on topic. As mentioned above |
I understand that there was a managed->unmanaged problem that they knew needed a pattern to solve from the very beginning of managed code, which is a large part of how Firstly, it's often applied because of the readability and convenience of the Additionally, there are many official capacities directly in the framework and language which use Dispose, as a pattern, has evolved to a point where it is used to ensure that the composition of a component graph (not to be confused with the Also, the recommended So now when I create an MVVM framework, if a parent view model is being destroyed I call We are now seeing very similar benefits keeping those design principles in mind from the beginning in a Blazor application (which, I'll note, advises exactly the pattern of use I describe here). Also, as far as a weak event publisher pattern is concerned, I would strongly advise against using |
I deal with this exact scenario to tear down my Direct3D rendering. That's what the Unloaded event does for you (it's fired when it's removed from the view hierarchy), or alternatively The problem with dispose is that it's use-once. However in for-instance a tab-control scenario, the control gets loaded/unloaded multiple times when you flip back and forth between tabs. There's even guidelines for holding onto a singleton of an expensive view control, and reuse it rather than creating new ones over and over again when you navigate back and forth between pages. |
Yet another WeakEvent implementation provided by: System.Waf.Core.
Example for INotifyPropertyChanged.PropertyChanged: public class Publisher : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
}
public class Subscriber
{
public void Init(Publisher publisher)
{
// Instead of publisher.PropertyChanged += Handler; use the following statement:
WeakEvent.PropertyChanged.Add(publisher, Handler)
}
public void Handler(object? sender, PropertyChangedEventArgs e) { }
} More details can be found on this Wiki page Weak Event. |
Honestly, the weak event pattern should not only be added to the runtime but enabled by default -- for ALL events. This is a long-standing issue with .NET on par with null reference exceptions. If they can add nullable reference types then can certainly make the events use weak references by default in the entire framework. This would eliminate a massive amount of memory leaks that really shouldn't happen so easily in 2022 in a managed language. |
I'm pretty sure this would break a lot of existing code... |
Yes default as weak would cause more problems than we're trying to solve. |
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. |
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 |
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. |
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:
would just be so delightful. |
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? 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 |
@thomaslevesque ah that's interesting! I would've guessed the class instance (or static class as the case may be) on which That raises an interesting point then about a hypothetical |
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. 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 |
As things currently stand, being referenced by a timer would prevent the object being marked for collection. |
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 Is there a situation where that best practice cannot be followed at all that would warrant these "weak events" as the only alternative? |
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. |
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 |
"Weak event" pattern is a widely used pattern. But still, there is no out-of-the-box support for it in .NET
The text was updated successfully, but these errors were encountered: