1

There is simple class with event:

public class BaseEventProvider {
    public event EventHandler SomeEvent;

    public void Publish() {
        SomeEvent?.Invoke(this, null);
    }
}

I know, that it is a difficult to unsubscribe from event, if you handle an event using lambda function and in major cases it leads to memory leaks. For example:

BaseEventProvider eventProvider = new BaseEventProvider();
eventProvider.SomeEvent += (s, e) => {
     Console.WriteLine("was handled!");
};

But why in such case unsubscribing works:

class Program {
    static void Main(string[] args) {
        BaseEventProvider eventProvider = new BaseEventProvider();
        eventProvider.SomeEvent += Handler();
        eventProvider.Publish();

        eventProvider.SomeEvent -= Handler();
        eventProvider.Publish();

        Console.ReadKey();
    }

    private static EventHandler Handler() {
        return (s, e) => {
            Console.WriteLine("was handled!"); 
        };
    }
}

Console output shows the event was only handled once:

was handled!

Is there something under the hood, probably after compilation and code optimization?

user2864740
  • 60,010
  • 15
  • 145
  • 220
Uladzimir Sharyi
  • 144
  • 1
  • 14
  • tldr; it's not a "memory leak", it's just code trying to remove a *non-existent* handler. The "memory leak" with events comes as Events/Producers keep a strong reference to Subscribers.. – user2864740 Jul 30 '18 at 21:27
  • 1
    You can unsubscribe by using `Handler()` because you've subscribed with the same reference. I.e `Handler() == Handler() == true` – JohanP Jul 30 '18 at 21:36
  • user2864740 thanks for remarks. I understand why it works with Method Groups as you said, because event has strong reference on the method like (private void Handler(object sender..,), but why unsibscription works, when we execute method which returns lambda(ananonymous method)? – Uladzimir Sharyi Jul 30 '18 at 21:38
  • @user2864740 when `Handler()` is called to unsubscribe it is unsubbing. It prints out `"was handled"` only once. – JohanP Jul 30 '18 at 21:53
  • @JohanP I had some major .. slip :} – user2864740 Jul 30 '18 at 21:54
  • @user2864740, It seemed to me that Handler() returned every time a new lambda function :) – Uladzimir Sharyi Jul 30 '18 at 21:54
  • 1
    @user2864740 Yep, it doesn't have to be static, still works – JohanP Jul 30 '18 at 21:56

1 Answers1

2

Since each lambda expression returns a different heap of code, it has a different MethodInfo data which you can extract like in here. Thus these are two different addresses of the methods and your attempt to unsubscribe from lambda expression would do nothing since you would be providing a different "method". Meanwhile, unsubscribing from a method refers to the same MethodInfo (objects are equal) and will succeed in unsubscribing.

Andrius Naruševičius
  • 8,348
  • 7
  • 49
  • 78
  • *Since each lambda expression returns a different heap of code* Can you provide reference for this? AFAIK, C# specification allows two semantically equivalent lambda expressions to be implemented by same method. – user4003407 Jul 30 '18 at 22:17
  • Conversions of semantically identical anonymous functions with the same (possibly empty) set of captured outer variable instances to the same delegate types are permitted (but not required) to return the same delegate instance. https://github.com/dotnet/csharplang/blob/master/spec/conversions.md#evaluation-of-anonymous-function-conversions-to-delegate-types – user4003407 Jul 30 '18 at 22:25
  • @PetSerAl If I understood you correctly, yes, that is allowed, but these expressions are treated as not equal (not runnable due to security reasons) https://dotnetfiddle.net/NyLp3K – Andrius Naruševičius Jul 30 '18 at 22:43
  • How do you know that them treated as not equal? Because handles are different? But them are *permitted (**but not required**)* to be the same. Current C# compiler may emit separate methods for each lambda, but it does not require to all future compiler to do the same. – user4003407 Jul 31 '18 at 09:28