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

Proposal: C# language support of "weak" keyword #13950

Closed
Opiumtm opened this issue Sep 21, 2016 · 23 comments
Closed

Proposal: C# language support of "weak" keyword #13950

Opiumtm opened this issue Sep 21, 2016 · 23 comments

Comments

@Opiumtm
Copy link

Opiumtm commented Sep 21, 2016

Provide weak keyword for support weak events and weak references in C#

Weak events support (#101):

someObject.Event += weak (sender, e) => {};
// translate to something like 
System.Runtime.WeakEvent.Attach(someObject, nameof(TSomeObject.Event), (sender, e) => {});

Same weak keyword may be used to simplify work with regular weak references

var obj = weak new List<string>();
// translate to 
var obj = new WeakReference<List<string>>(new List<string>());

public class AClass
{
    private weak List<string> weakList;
}

// translate to
public class AClass
{
    [WeakReference]
    private WeakReference<List<String>> weakList;
}

public weak List<string> SomeFunc()
{
     return new List<string>();
}
// translate to
[WeakReference]
public WeakReference<List<string>> SomeFunc()
{
    return new WeakReference<List<string>>();
}

As WeakRefecence<T> can not contain null - assignment of null should be used to indicate weak reference itself is null.

List<string> hardList;
weak ListString<string> weakList = hardList;
// translate to
if (hardList == null)
{
    weakList = null;
} else
{
    weakList = new WeakReference<List<string>>(hardList);
}

Usage of declared weak references in code

// declare weak reference
weak List<string> weakList = new List<string>();
// same as
var weakList = weak new ListString();
// first declaration example is explicit variable type declaration, second - using "var" implicit typing

var cnt = weakList.Count;
// translate to:
int cnt;
{
    List<string> __weak_t;
    if (weakList != null && weakList.TryGetTarget(out __weak_t))
    {
        cnt = __weak_t.Count;
    } else
    {
        throw new InvalidWeakReferenceException();
    }
}

List<string> hardList = weakList;
// translate to
List<string> hardList;
if (weakList == null || !weakList.TryGetTarget(out hardList))
{
    throw new InvalidWeakReferenceException();
}

// use ?. operator to not throw exception if weak reference is invalid
var cnt = weakList?.Count;
// translate to
int? cnt;
{
    List<string> __weak_t;
    if (weakList != null && weakList.TryGetTarget(out __weak_t))
    {
        cnt = __weak_t.Count;
    } else
    {
        cnt = null;
    }
}

// adding ? at the end to not throw exception
List<string> hardList = weakList?;
// translate to
List<string> hardList;
if (weakList == null || !weakList.TryGetTarget(out hardList))
{
    hardList = null
}

// Checking if weak reference is alive by using ? operator
if (weakList? != null)
{
    Console.WriteLine("Weak reference is alive!");
}
// translate to
{
    List<string> __weak_t;
    if (weakList == null || !weakList.TryGetTarget(out __weak_t))
    {
        __weak_t = null;
    }
    if (__weak_t != null)
    {
        Console.WriteLine("Weak reference is alive!");
    }    
}

// Call a method
weakList.Clear();
// translate to
{
    List<string> __weak_t;
    if (weakList == null || !weakList.TryGetTarget(out __weak_t))
    {
        throw new InvalidWeakReferenceException();
    }
    __weak_t.Clear();
}

// Call a method not throwing an exception if reference is invalid
weakList?.Clear();
// translate to
{
    List<string> __weak_t;
    if (weakList != null && weakList.TryGetTarget(out __weak_t))
    {
        __weak_t.Clear();
    }
}

And types

var t = typeof(weak List<string>);
// same as
var t = typeof(WeakReference<List<string>>);

weakList.GetType() // result: typeof(WeakReference<List<string>>)
weakList?.GetType() // result: typeof(List<string>)
@Opiumtm Opiumtm changed the title Language proposal for "weak" keyword C# language support of "weak" keyword Sep 21, 2016
@Opiumtm Opiumtm changed the title C# language support of "weak" keyword Proposal: C# language support of "weak" keyword Sep 21, 2016
@MgSam
Copy link

MgSam commented Sep 21, 2016

This seems like a heavy solution to a relatively rare problem. How often do you need to use weak references such that introducing a whole new language feature is worth the significant cost?

@Opiumtm
Copy link
Author

Opiumtm commented Sep 21, 2016

@MgSam Quite often. Weak references are often used in XAML apps. Especially often used is so called "weak event" pattern (it is supported out-of-the box in WPF, but isn't supported out-of-the-box in Silverlight and Windows Runtime/UWP XAML apps). So, weak references and weak events are common practice in complex UI frameworks and apps.

Proposal for weak keyword was done in general with #101 proposal for "weak event" pattern. This would generalize weak reference language support (not only support for weak events, but also for weak references in general at language level) and finish this topic.

Weak references are often used when lifetime of some objects isn't determined by garbage collector, but by external means - such as windows or pages user navigation or framework-controlled construction and destruction of UI controls. "Object graph visibility" lifecycle paradigm is often breaking here - as lifetime of many objects is determined by external means and if object with externally controlled lifetime is holding reference (often within event handler delegate) to other objects graph this would produce memory leaks or other undesired effects.

It's why weak references are often used in XAML UI apps.

@Opiumtm
Copy link
Author

Opiumtm commented Sep 22, 2016

It may be somewhat generalized to concept of "virtual references"

public interface IVirtualReference<T> where T : class
{
    bool TryGetTarget(out T target);
}

Interface signature is exact as of WeakReference<T> class

"Virtual references" may support language-level dereferencing

IVirtualReference<List<string>> vref;
List<string> list = vref;
// translate to
List<string> list;
if (vref == null || !vref.TryGetTarget(out hardList))
{
    throw new InvalidVirtualReferenceException();
}

...and ?. operator and "?" dereference without throwing an exception.

// Call a method not throwing an exception if reference is invalid
vref?.Clear();
// translate to
{
    List<string> __vref_t;
    if (vref != null && vref.TryGetTarget(out __vref_t))
    {
        __vref_t.Clear();
    }
}

So this would make weak reference the special case of generalized virtual reference.

Virtual references would be useful for a wide range of cases, including proxies of any kind and interop scenarios.
It's another application for already introduced ?. operator.

@ufcpp
Copy link
Contributor

ufcpp commented Sep 22, 2016

@Opiumtm Weak references has some needs, but its needs is not so much.
On the other hand, C# wants to be more and more a High-Performance Language (see Themes in #98). Weak references goes against this theme. It has large negative impact on performance.

@Opiumtm
Copy link
Author

Opiumtm commented Sep 22, 2016

@ufcpp

but its needs is not so much

Are you sure? Weak references are very much used in XAML UI apps. I posted above why they're used in XAML UI apps.

Weak references are often used when lifetime of some objects isn't determined by garbage collector, but by external means - such as windows or pages user navigation or framework-controlled construction and destruction of UI controls. "Object graph visibility" lifecycle paradigm is often breaking here - as lifetime of many objects is determined by external means and if object with externally controlled lifetime is holding reference (often within event handler delegate) to other objects graph this would produce memory leaks or other undesired effects.
It's why weak references are often used in XAML UI apps.

WPF framework even have built-in support for weak reference patterns ("weak event" pattern in particular).

I agree, weak references as an "automatic tool" to solve UI-related object lifetime complexity (root cause for all these issues is a fact that any UI framework impose its own object lifetime scheme completely unrelated to GC "object visibility graph" paradigm as it is primarily driven by user interaction) isn't optimal at all, but it greatly simplify complex UI logic.

Most painful point in all .NET UI frameworks (and XAML UI in particular) is a fact that delegate is capturing hard reference to object and attachment of delegate to the UI control event effectively create object lifetime dependency on UI control via this delegate. To avoid this lifetime dependency event handlers must be manually removed (so you should maintain it in code) or to simplify things "weak event" pattern is used - especially if you're using or developing UI MVVM frameworks or helpers which haven't much knowledge about exact UI logic and structure of a particular app.

You are talking from the server-side perspective. Indeed, ASP.NET code haven't such painful dependencies on "external object lifetime" and should deliver best performance. But client-side UI code have these object lifetime dependencies (and major object lifetime dependency is the user UI actions themselves). Generalized solution applicable to MVVM frameworks and helpers (not knowing exact UI logic and structure) is to use weak references to avoid undesired implicit object lifetime dependencies.

Implicit lifetime dependencies on UI controls lifetime is a major source of memory leaks in XAML UI applications.

For example, Windows Runtime/UWP XAML app model support so called "navigation caching" and "UI virtualization caching" when UI controls and UI pages instances are re-used by framework if possible. Implicit object lifetime dependencies on these UI objects can cause massive memory leaks.

@qrli
Copy link

qrli commented Sep 23, 2016

@Opiumtm
Typically, you should not need to explicitly use weak events in XAML apps in majority scenarios. It is necessary for some low-level or reusable library components. But for high-level application code, it is usually not the right solution.

The dangling event handlers are not only a memory leak issue. They may still run in case event is raised, and there is no guarantee when GC will actually remove the objects. So even if you have weak events, you have only solved half of the problem, if it is a real problem to solve in you app.

Typical case should be that your UI event handler and your ViewModel have the same lifetime as your UI, so you don't need to use weak events nor unsubscribe anything. If event emitter's life is longer than handler, it is always better to unsubscribe the event handler explicitly.

@sandersaares
Copy link

Weak event handlers are quite important in UI code and in situations where the inversion of control pattern is heavily applied. Novice developers often make mistakes, leaving event handlers attached and causing leaks. Language support for weak event handlers would be quite desirable!

@Opiumtm
Copy link
Author

Opiumtm commented Sep 23, 2016

@qrli

Typically, you should not need to explicitly use weak events in XAML apps

But in reality I am often forced to do so.
How do you implement subscription to some global event defined in static or long-living singleton object?

One example is a DisplayInformation on Universal Windows Platform. It's a global singleton object and if you subscribe to events such as DPI change (if your app is starting to be projected on PC display using Continuum feature), you will introduce memory leak. Also, Universal Windows apps typically have only one global Window object and if you subscribe to its events - you are forced to use weak events.

One another example is a subscription to global application events such as "Suspending" to do view-model specific work on application suspending (save its internal state to disk, obviously). Application is a global static object. If you attach events directly without weak event - you will immediately introduce memory leak.

@Opiumtm
Copy link
Author

Opiumtm commented Sep 23, 2016

@qrli

The dangling event handlers are not only a memory leak issue. They may still run in case event is raised, and there is no guarantee when GC will actually remove the objects. So even if you have weak events, you have only solved half of the problem, if it is a real problem to solve in you app.

For the most cases it isn't a problem.
When you finished with view model, you can dispose it explicitly or some other way indicate that view model is no longer valid, so view model wouldn't react to any events.

If event emitter's life is longer than handler, it is always better to unsubscribe the event handler explicitly.

You are wrong. On the view-model side you don't always know if your method is used as event handler (after all, Universal Windows XAML platform allow to bind void SomeMethod() as XAML event handlers) or bound to some delegating ICommand. View model fundamentally doesn't know how it is used from the outside and shouldn't know it. View model have no exact knowledge on how and where it is used in actual XAML markup UI.

And even if view model indeed subscribe to XAML events, it should maintain it and unsubscribe from all event handlers - which introduce lots of boilerplate and error-prone code. Weak event handlers is not optimal, but very simple and quite universal solution to avoid manual event handlers management.

Weak event handlers + view model state (alive or disposed) is the universal and simple solution to handle problems you described. Call Dispose() on view model once when you are finished with view model is much simpler than manually unsubscribe all the event handlers and maintain event handlers lists (and you may not have exact knowledge on such view model usage). View model would not react on any events in disposed state.

@Opiumtm
Copy link
Author

Opiumtm commented Sep 23, 2016

@qrli
And to conclude this topic, I must remind you one very useful practical rule.
You should use weak events when you don't know about target object lifecycle or when its lifecycle is independent from your object.

When two objects have independent lifecycles, controlled by independent means - weak events and weak references should always be used between them to avoid undesired implicit lifecycle dependencies.

@ufcpp
Copy link
Contributor

ufcpp commented Sep 23, 2016

What you really need is some sort of safer and easier way for disposable pattern, unsubscribing events is one of them. Weak events might not be good way for it, and looks like C# team is looking for better way.

@Opiumtm
Copy link
Author

Opiumtm commented Sep 23, 2016

@ufcpp as for now, weak references and weak events are commonly used in XAML UI apps. "Weak event" pattern was originally introduced in XAML WPF and as Silverlight and Windows Runtime/UWP doesn't support it out-of-the-box (as WPF does) it's perpetually reinvented in any XAML UI MVVM framework and library. Template10 UWP library, for example, not an exception - this lib again reinvented its own weak event helper classes and use weak events in library itself. As I have commited to Template10 library source code repository, I certainly know how and why it is used.

Template10 lib classes does subscribe on global singleton system events and without weak events it would introduce memory leaks.

@Opiumtm
Copy link
Author

Opiumtm commented Sep 23, 2016

@ufcpp
Here is example of weak event using in Template10 lib
https://github.com/Windows-XAML/Template10/blob/af72162201820343e62e5cfd8309d9e288a30880/Template10%20(Library)/Utils/DeviceUtils.cs

        private DeviceUtils(Common.WindowWrapper windowWrapper)
        {
            MonitorUtils = MonitorUtils.Current(windowWrapper);
            WindowWrapper = windowWrapper ?? Common.WindowWrapper.Current();

            var di = windowWrapper.DisplayInformation();
            di.OrientationChanged += new Common.WeakReference<DeviceUtils, DisplayInformation, object>(this)
            {
                EventAction = (i, s, e) => i.Changed?.Invoke(i, EventArgs.Empty),
                DetachAction = (i, w) => di.OrientationChanged -= w.Handler
            }.Handler;

            var av = windowWrapper.ApplicationView();
            av.VisibleBoundsChanged += new Common.WeakReference<DeviceUtils, ApplicationView, object>(this)
            {
                EventAction = (i, s, e) => i.Changed?.Invoke(i, EventArgs.Empty),
                DetachAction = (i, w) => av.VisibleBoundsChanged -= w.Handler
            }.Handler;
        }

@qrli
Copy link

qrli commented Sep 24, 2016

@Opiumtm
1st, all data bindings and command bindings are already weak referenced. You dont need another layer of weak reference.
2nd, once you use bindings, your view model won't have many event subscription from view. Even if you have, it typically does not matter because view-model typically has the same lifetime as view.
3rd, view-model are designed for views so it is called view-model. If you have a public event handler method, you know it would be used as event handler, strong-referenced.
4th, the only typical troublesome case, as you mentioned, is view-model subscribing events from singleton or other long-lived objects. In such scenarios, you should unsubscribe them on view close, and it is easier than to remember to check for view/view-model disposed state in every event handler, which is much more ease to introduce hard to reproduce bugs.

As for the case you don't know the actual lifecycle of objects, I proactively avoid that. Even in Javascript or python code, I do care about lifecycle, unless the process run once and then exit.

@Opiumtm
Copy link
Author

Opiumtm commented Sep 25, 2016

@qrli you may be right theoretically, but in practice weak references are commonly used in client XAML UI apps and was used in Windows Forms apps too.
When I personally program server-side ASP.NET or Windows Service code I never use weak references, this runtime feature is almost never needed in server-side code. But in client-side code it's used very often. Maybe it's because of absence of better tools for UI client apps. But it's a practice commonly used in .NET UI for years.

@Opiumtm
Copy link
Author

Opiumtm commented Oct 10, 2016

dotnet/corefx#12007

@ygc369
Copy link

ygc369 commented Feb 6, 2017

@ufcpp
I agree that the current weakreference has large negative impact on performance, but I think there should be solution to the problem. See #2171, weak ref should not have extra cost compared with strong ref.

@ali-hk
Copy link

ali-hk commented Jun 3, 2017

@qrli weak events are very much a necessity when it comes to UWP XAML apps that have custom behaviors (and controls derived from built-in controls). Because the controls themselves are native C++, subscribing to an event such as KeyUp creates a ref cycle that the GC can't know about and the ref counted C++ will therefore always have a non-zero ref count. This is even more of a problem with custom C++ controls (non-framework) that are consumed by C#.
There is no good way to disconnect the event handlers in question, because Unloaded is not called synchronously, meaning if you were to unsubscribe in Unloaded, another Loaded event could have come in in the meantime and you'd be breaking your control (i.e. the element got re-added to the visual tree. Loaded, Loaded, Unloaded (corresponding to the first Loaded)).
The net result is a leaked control and possibly everything that touches that control depending on the circumstances.
This is a very common issue that there currently isn't a good workaround for other than creating weak event handler workarounds.

@pquiring
Copy link

pquiring commented Jul 1, 2018

I would like to see the 'weak' modifier added, even if the C# language does not use it, but it would benefit others that use the compiler for other projects unrelated to a .NET framework (ie: do not use garbage collection).

I often use attributes to signal a weak reference::

[Weak]
MyObject some_variable;

But using a keyword would be much more natural.

Thanks.

@EricOuellet2
Copy link

Just a word to give you my support to add a Weak Event support to the language anyhow it could be implemented. More peoples will ask for it, more chances we have to get it one day! Nice work!

@cosminstirbu
Copy link

cosminstirbu commented Oct 23, 2019

Hello,

I would also like to see support for a "weak" keyword built into the language especially because the interoperability with iOS forces us to be extra careful of avoiding strong reference cycles.

Xamarin.iOS already has a [Weak] attribute that can be used for fields. However it would be useful if we could extend it for properties, events and delegates as well (actions and funcs).

There are often scenarios when using an action makes more sense than using an interface, and we're forced to use an interface just because it's easier to create a weak reference for it.

There are a few open source alternatives such as https://github.com/thomaslevesque/WeakEvent and https://github.com/lbugnion/mvvmlight/blob/master/GalaSoft.MvvmLight/GalaSoft.MvvmLight%20(PCL)/Helpers/WeakActionGeneric.cs but in my opinion they still require significant boiler plate / support code.

Giving that .NET 5 is heading towards closer interoperability with Swift / Objective-C this "weak" keyword would be very helpful.

Thank you,
Cosmin

@sjb-sjb
Copy link

sjb-sjb commented Mar 18, 2022

I am in favor of the proposal for the weak keyword. Overall the use of weak events and weak collections introduces significant new flexibility into the language. Language support for WeakReferences makes the WeakReference class far easier to use, and if we believe that WeakReference is a very useful class then langauge support for it is merited.

(1) In the MVVM paradigm, the suggested approaches that avoid weak references require the model elements to be rooted by the View and ViewModel. This is not necessarily the desired design approach as there are many cases where the Model element needs to be shared across multiple Views and must therefore have a lifetime exceeding the View and ViewModel lifetime. If the Model element has events then these need to be weak events because as we know there is not always a consistent place to call View.Dispose or ViewModel.Dispose for every View element (per @ali-hk the Unloaded event in particular is not such a place).

(2) While it is true that the use of weak events can result in event callbacks occurring after the element (a ViewModel or View in the examples) is intended to be out of use, this is already a problem that needs to be solved for because in c# an event callback can occur after the event is unsubscribed from. Specifically the unsubscribe can occur after the event is raised but before all of the event callbacks complete ("zombie sender"). Therefore one needs to always check the sender in event callbacks anyway, as a general programming practice. So the suggestion that one should explicitly unsubscribe from events in Dispose does not address the concern that callbacks can occur after the item is intended to be out of use. In addition, in practice zombie callbacks from Model elements into ViewModel or View elements are seldom problematic because the callback action in the ViewModel or View is typically harmless.

@CyrusNajmabadi
Copy link
Member

Closing this out. We're doing all language design now at dotnet/csharplang. If you're still interested in this idea let us know and we can migrate this over to a discussion in that repo. Thanks!

@CyrusNajmabadi CyrusNajmabadi closed this as not planned Won't fix, can't repro, duplicate, stale Nov 8, 2022
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