18

Is it possible in C# to connect one event to another so emitting first event will emit second one? The only way i can see is to create a stub function that will emit second event and connect the first event to this function. Compiler do not want to connect event to event or event to anonymouse function / lambda that calls another event:

class Ui { public event EventHandler OnClick; }
class Logic { public event EventHandler OnExit; }
var ui = new Ui();
var logic = new Logic();
ui.OnClick += logic.OnExit; // Not working.
ui.OnClick += ( a, b ) => logic.OnExit; // Not working either :(.

Maybe it's some decorator available or some black magic that allows to chain events without stub functions?

grigoryvp
  • 40,413
  • 64
  • 174
  • 277

2 Answers2

10

You cannot do this, because you generally cannot do anything to an event from outside the object which owns it except for adding and removing handlers. In particular, you cannot list the existing registered handlers, and you cannot raise it. In your case, "copying" the event is essentially the same thing in disguise, and would allow you to circumvent this restriction; therefore, it's not allowed.

See this recent answer of mine for a more in-depth explanation of why things are the way they are - I just don't feel like retyping it all here.

For your particular case, if you own both classes, the workaround is to make them cooperate specifically - make Ui be aware of the associated Logic instance, and add event handlers to Logic.OnClick in Ui.OnClick.add implementation. Of course, this introduces coupling; you can reduce it to some extent by using more generic interfaces, but you can't get rid of it entirely.

As a side note, OnClick is not a good name for a .NET event. Common naming guide says that it should be simply Click (and OnClick should be the name of a protected virtual method that raises it).

Community
  • 1
  • 1
Pavel Minaev
  • 99,783
  • 25
  • 219
  • 289
  • 3
    This introduce coupling - that says it's all ^_^. – grigoryvp Oct 29 '09 at 21:33
  • That's precisely the point. List of handlers is private to the object which owns the event, by design. The reason is that object alone should control when and how the handlers are invoked. It's basic encapsulation. If you want to break encapsulation, you'll need to provide a special hatch for it, and your classes need to be aware of it - hence coupling. What you're asking for is really no different from "can I access the private field of one of my classes from another?". – Pavel Minaev Oct 29 '09 at 21:38
  • So basically all i need to do is to move the subscription code into a class that owns the second event? :) – grigoryvp Oct 29 '09 at 21:58
  • Not sure which one of them is second :) you need to move it into the class which owns the "source" event (the one, addition of handler to which should result in the same handler added to the "target" event). – Pavel Minaev Oct 29 '09 at 22:35
  • Thank you, Pavel. I have checked that on VS2010 with .net 2.0 target and all works fine: public Form1() { InitializeComponent(); button1.Click += (a,b) => EExit( a, b ); } public event EventHandler EExit; Unfortunately, connecting directly button1.Click += EExit doesn't work (compiles, no exception - just method subscribed to EExit not called) but i think it's a language limitation and is not important at all - lambda expression is not a long one :) – grigoryvp Oct 30 '09 at 05:50
  • Since you are working in Microsoft on VS2010 i can be more specific: this is for implementing "compound view" concept where a window class (a View) contains controls (they are views to) and i want to connect control's "OnClick" event to the window's "OnSomething" event. This is due the fact that most of button clicks don't need any CODE in window's view - they clicks just need to fire a window's "higher level" event. And that event is connected to the controller/presenter. – grigoryvp Oct 30 '09 at 05:58
  • `button1.Click += EExit` doesn't work because in that expression `EExit` references the backing field (of delegate type) of the event. Since you probably do it in your cosntructor, the field is `null`, so `+=` is a no-op at that point. You don't establish any kind of connection between two events that way, so when event `EExit` gets handlers registered later, and the backing field is updated, this has no effect on `Click`. With a lambda, you're actually adding one single handler to `Click` which says "raise event `EExit`". Since it'll read the value of field `EExit` on every call, it's in sync – Pavel Minaev Oct 30 '09 at 06:50
  • The pattern you describe (exposing events of child controls on parent control) is pretty common, and if your `EventArgs` types are the same, you can actually do it even simpler: see http://stackoverflow.com/questions/943171/expose-and-raise-event-of-a-child-control-in-a-usercontrol-in-c. If `EventArgs` are different and you need to map them, using a lambda as you do is probably as simple as it gets. – Pavel Minaev Oct 30 '09 at 06:53
2

You could do this by hiding the underlying event for Logic and then controlling calls to Add/Remove which require a UI parameter.

public class UI {
  public EventHandler OnClick;
}
public class Logic {
  private event EventHandler _onExit;
  public void AddOnExit(UI ui, EventHandler e) {
    ui.OnClick += e;
    _onExit += e;
  }
  public void RemoveOnExit(UI ui, EventHandler e) {
    ui.OnClick -= e;
    _onExit -= e;
  }
}
JaredPar
  • 733,204
  • 149
  • 1,241
  • 1,454
  • 2
    In his example, he's not trying to link two events of the same object - he's trying to link sa pair matching events between two different objects. – Pavel Minaev Oct 29 '09 at 21:31
  • @Pavel, ah OK. The same logic works though between two objects if they are otherwise connected. But yes, slightly different than my example, will udpate – JaredPar Oct 29 '09 at 21:32