Let's imagine an example of a presentation layer. The layer has mouse interactions: every time the user moves a mouse, an event telling which mouse moved (imagine the user may have multiple mice) and how the mouse moved can be raised.
With your approach, the draft code would be:
public delegate void MouseMovedHandler(
PresentationLayer l, Guid mouseId, int fromX, int fromY, int toX, int toY);
It will obviously be quickly refactored into:
public delegate void MouseMovedHandler(
PresentationLayer l, Guid mouseId, Coord from, Coord to);
But then, you'll find that you want to know, for instance, the distance between from
and to
. So you'll create a Line
class, defined by two points, and your delegate will become:
public delegate void MouseMovedHandler(PresentationLayer l, Guid mouseId, Line motion)
Better. Now, what if we want to be able to make a difference between a simple line and a motion of a specific, identified mouse? Right, we'll end up creating the class:
class MouseMotion : Line
{
public Mouse Mouse { get; }
public Line Motion { get; }
...
}
public delegate void MouseMovedHandler(PresentationLayer l, MouseMotion motion)
Now, there is a location in your app where you need to track every possible input: mouse motion, mouse button clicks, keyboard strokes. You need that because your application starts background processing when the user is not doing anything, but should stop it and free the resources to the UI if the user is using your app.
This means that now, you need something common between MouseMotion
, MouseButtonChange
and KeyboardStroke
. Something like IEvent
. Or wouldn't it be easier to simply use EventArgs
?
So you end up with:
class MouseMotionEventArgs: EventArgs { ... }
public delegate void MouseMovedHandler(PresentationLayer l, MouseMotionEventArgs motion)
Except that you could avoid the delegate, as explained at the beginning of the answer. You can now either stick with the delegate in order to have your strongly typed PresentationLayer
you probably don't need most of the time, or replace it with a lousy object
by using EventHandler<MouseMotionEventArgs>
.
The previous version of the answer, for the sake of completeness:
Is there a reason you create a CustomEventHandler
instead of using EventHandler<CustomEventArgs>
?
With the generic EventHandler<T>
, the code starts looking like this:
public class Foo
{
public event EventHandler<3DPointChangedEventArgs> CustomEvent;
public void OnCustomEvent(3DPoint point)
{
if (CustomEvent != null)
{
CustomEvent(this, (3DPointChangedEventArgs)point);
}
}
}
Now, compare it to the code you suggested:
Talking specifically about name pollution, having *EventArgs
classes is not that bad. Those names are usually very clear and express the intention of the author and cannot be confused with other business objects.
Answering your last edit, there are three points to consider:
It is not necessarily useful to separate the death from the health. The most basic representation of health could be a number; for instance, 0.0 can represent death and 100.0 can correspond to the full health.
Of course, a number would better be replaced by Health
class which can then contain business rules which can be modified through time. For example, if it is a game, further features may include additional health slots, making it possible to have, say, 120% of health.
This may create a performance issue: one event less means that if something is listening to the death events, it will now be notified about all the health change events. I wouldn't be concerned about that early, and wait until it becomes an actual bottleneck. After all, what if the performance becomes even better, now that we don't have a condition in ApplyDamage()
?
If the author decides to keep both events, death event can use EventArgs
, making it possible to use EventArgs.Empty
. This makes one delegate less to write.
Who listens to the event? Let's imagine the world listens to it in order to remove objects a few minutes after their death (which makes sense, since objects may not be aware of the world, and so won't be able to remove themselves from it).
This also means that we don't really care if the dead object is a Character
or a Trebuchet
or a WarShip
or a Horse : Animal
. This means that it is OK to lose types when transmitting the information through the event: the removal will be the same for a paladin or a sheep.
Here's the final result which applies the point 1 and 3:
public class Health
{
private double value;
public Health(double value) { ... }
public void ApplyDamage() { ... }
}
public class Character
{
public event EventHandler<HealthEventArgs> HealthChanged;
public Health Health { get { return this.health; } }
public void ApplyDamage(double damage)
{
...
this.health.ApplyDamage(damage);
...
}
public void OnHealthChange(float damage)
{
if (HurtEvent != null)
{
HurtEvent(this, damage);
}
}
}
The events handling can then be done like this, making abstraction of the type of the world object being removed:
public class World
{
public void MarkAsDead(object worldObject)
{
// Wait for five minutes.
this.elements.Remove(worldObject);
}
}
Best Answer
There actually is this sort of distinction made under the covers.
Delegate
is the single function wrapper, andMulticastDelegate
is the type that is the composite form ofDelegate
. The confusing part is thatDelegate
isn't really exposed from the innards of the runtime. All delegates created by C# are multicast delegates, even if they only contain a single item.This is because historically, delegates were largely there to make windows events work nicely, which often would have multiple listeners. Now that delegates are used more for functional programming approaches, it's a little more awkward in theory. In practice, very few people use the multicast functionality outside of events given its downsides (exceptions at any point of the chain will break it, and the caller won't know where, hard to debug) and because it's not exactly common knowledge.