C
C#5mo ago
Tom

✅ Design of events

Hi! I have a question about utilization of events. Don't know many real life production examples by myself, but counting on your experience 😉 I've noticed that EventHandler delegate takes two parameters: object sender and EventArgs or other defined type. That means that in subscribers we can't be sure what is the sender and what are its properties/methods. So what's the main purpose (by design) of this "sender" parameter, shall it be used to check what type it is and take actions based on that? I am aware that I can create my own type of event instead of using EventHandler, but anyway I'm curious about the real life usage of the sender object.
17 Replies
Angius
Angius5mo ago
That's specifically about WPF events? If so, then the sender is a reference to the control that called the event Like, a click event of a button will have a reference to that button as the sender Other than that, the sender is generally a reference to the calling class
Tom
TomOP5mo ago
Thanks, I didn't mean WPF specific but is it a general rule that subscribers should be prepared that event can be raised by different type of classes? Otherwise, I think we could use other handler like: public delegate void CustomHandler<SenderType, EventArgsType>(SenderType sender, EventArgsType e). So the main reason of my question is that I want to understand why authors utilize this ambigous "object" as a sender. Probably there is some logic behind that but I know too litle real life examples to deduct that.
Angius
Angius5mo ago
There's a EventHandler<T> override that takes the type of the events object As to why...? Old code would be my best bet Someone did it this way some days, nobody bothered to update it because it would be a breaking change or whatever, and that's where we're at For custom code, I would probably just not use EventHandlers but rather Funcs and Actions, or a named delegate instead
MODiX
MODiX5mo ago
Angius
REPL Result: Failure
class Caller
{
public Action<DateTime, int>? Event { get; set; }

private int _counter = 0;

public void Add()
{
Console.WriteLine($"Counter is at {_counter}");
if (_counter++ > 10)
{
Event?.Invoke(DateTime.Now, _counter);
Environment.Exit(0);
}
}
}

class Subscriber
{
public Subscriber(Caller c)
{
c.Event = React;
}

void React(DateTime dt, int count)
{
Console.WriteLine($"Reaction at {dt:s} with count {count}");
}
}

var c = new Caller();
var s = new Subscriber(c);

for (var i = 0; i < 200; i++)
{
c.Add();
}
class Caller
{
public Action<DateTime, int>? Event { get; set; }

private int _counter = 0;

public void Add()
{
Console.WriteLine($"Counter is at {_counter}");
if (_counter++ > 10)
{
Event?.Invoke(DateTime.Now, _counter);
Environment.Exit(0);
}
}
}

class Subscriber
{
public Subscriber(Caller c)
{
c.Event = React;
}

void React(DateTime dt, int count)
{
Console.WriteLine($"Reaction at {dt:s} with count {count}");
}
}

var c = new Caller();
var s = new Subscriber(c);

for (var i = 0; i < 200; i++)
{
c.Add();
}
Console Output
Counter is at 0
Counter is at 1
Counter is at 2
Counter is at 3
Counter is at 4
Counter is at 5
Counter is at 6
Counter is at 7
Counter is at 8
Counter is at 9
Counter is at 10
Counter is at 11
Reaction at 2024-07-25T16:11:25 with count 12
Counter is at 0
Counter is at 1
Counter is at 2
Counter is at 3
Counter is at 4
Counter is at 5
Counter is at 6
Counter is at 7
Counter is at 8
Counter is at 9
Counter is at 10
Counter is at 11
Reaction at 2024-07-25T16:11:25 with count 12
Exception: ExitException
- Script exited with code 0
- Script exited with code 0
Compile: 705.722ms | Execution: 68.717ms | React with ❌ to remove this embed.
Angius
Angius5mo ago
Something like this, without using EventHandler
Tom
TomOP5mo ago
thx for your input and the example 😉 Probably I will have to find my way of dealing with that, but that comes with time good to know many ways to solve things
SleepWellPupper
SleepWellPupper5mo ago
It's object because that's the common abstraction and simplest to code against for the purpose of the parameter, which you have summarized perfectly. I agree with @ZZZZZZZZZZZZZZZZZZZZZZZZZ , you'd want an event handler delegate that matches the specific usecase. Sometimes that includes checking the sender due to subscriptions to several publishers of the same event.
canton7
canton75mo ago
Also, that pattern comes from C# 1, before we had generics But, generics will only take you so far. Sure you could do:
public class A
{
public event EventHandler<A, EventArgs> Evt;
}
public class A
{
public event EventHandler<A, EventArgs> Evt;
}
But what if A was subclassed?
public class B : A
{
}
public class B : A
{
}
Then you could do:
var b = new B();
b.Evt += (o, e) => ...
var b = new B();
b.Evt += (o, e) => ...
And that sender parameter would be typed as an instance of A, even though it was an instance of B which raised it. That happens loads in UI frameworks, where events are defined by classes near the base of the object hierarchy, but you often interact with classes much further down Obviously if you subscribe to TextBox.TextChanged you expect the sender parameter to be an instance of TextChanged. You're not going to get some other control raising that event and inserting itself as the sender parameter. But sometimes you have a list of 20 textboxes, and you want to do something when the text changes in any of them, and you don't want to define 20 handlers. So you define a single handler, and you use that sender parameter to interact with the specific textbox which raised the event Do bear in mind the reason that the EventHandler pattern exists, which is forwards compatibility. You can't add extra parameters to an Action without it being a breaking change, but you can add additional properties to an EventArgs subclass That's why ☝️
Tom
TomOP5mo ago
Thanks for your insight @canton7. then won't having specific TextBox as a sender class be good enough? However I may imagine that there are kinds of text boxes which you deal with differently...
canton7
canton75mo ago
Isn't that is what happens though?
Tom
TomOP5mo ago
@canton7 Not sure I understood you, as I mean typing instead of object sender, TextBox sender (or smth like that). But anyway, I'm now playing around a bit with Avalonia UI and its events, which may give me more insight, then I may "feel" more what you have writtten. Thank you both @ZZZZZZZZZZZZZZZZZZZZZZZZZ and @canton7 Btw. It's my first post in this channel, should I mark it as "answered" or smth like that?
canton7
canton75mo ago
$close
MODiX
MODiX5mo ago
If you have no further questions, please use /close to mark the forum thread as answered
canton7
canton75mo ago
Not sure I understood you, as I mean typing instead of object sender, TextBox sender (or smth like that).
Happy to clarify, but I'm afraid I'm not sure what your confusion is
Tom
TomOP5mo ago
@canton7 You've written:
Obviously if you subscribe to TextBox.TextChanged you expect the sender parameter to be an instance of TextChanged. You're not going to get some other control raising that event and inserting itself as the sender parameter. But sometimes you have a list of 20 textboxes, and you want to do something when the text changes in any of them, and you don't want to define 20 handlers. So you define a single handler, and you use that sender parameter to interact with the specific textbox which raised the event
I've assumed that you meant using object sender instead of e.g. TextBox.TextChanged sender make that (interaction without having 20 handlers) possible. I thought the latter makes it possible too (and the type in handler parameter precise kind of sender you are dealing with).
canton7
canton75mo ago
Oops, * Obviously if you subscribe to TextBox.TextChanged you expect the sender parameter to be an instance of TextBox. My point was that there's no harm casting the object sender to TextBox, since you know that it will always be an instance of TextBox Hmm, no, I can't spot it
Tom
TomOP5mo ago
Thanks, now I get your point. I think I'll try to use strongly typed param in handler definition, unless some framework force me to use object. But I may of course change my opinion after having some more practice 😉 Good talk, thank you, I'm closing the topic now. See you! 🙂
Want results from more Discord servers?
Add your server