Asked  7 Months ago    Answers:  5   Viewed   36 times

I understand the purpose of events, especially within the context of creating user interfaces. I think this is the prototype for creating an event:

public void EventName(object sender, EventArgs e);

What do event handlers do, why are they needed, and how do I to create one?

 Answers

50

To understand event handlers, you need to understand delegates. In C#, you can think of a delegate as a pointer (or a reference) to a method. This is useful because the pointer can be passed around as a value.

The central concept of a delegate is its signature, or shape. That is (1) the return type and (2) the input arguments. For example, if we create a delegate void MyDelegate(object sender, EventArgs e), it can only point to methods which return void, and take an object and EventArgs. Kind of like a square hole and a square peg. So we say these methods have the same signature, or shape, as the delegate.

So knowing how to create a reference to a method, let's think about the purpose of events: we want to cause some code to be executed when something happens elsewhere in the system - or "handle the event". To do this, we create specific methods for the code we want to be executed. The glue between the event and the methods to be executed are the delegates. The event must internally store a "list" of pointers to the methods to call when the event is raised.* Of course, to be able to call a method, we need to know what arguments to pass to it! We use the delegate as the "contract" between the event and all the specific methods that will be called.

So the default EventHandler (and many like it) represents a specific shape of method (again, void/object-EventArgs). When you declare an event, you are saying which shape of method (EventHandler) that event will invoke, by specifying a delegate:

//This delegate can be used to point to methods
//which return void and take a string.
public delegate void MyEventHandler(string foo);

//This event can cause any method which conforms
//to MyEventHandler to be called.
public event MyEventHandler SomethingHappened;

//Here is some code I want to be executed
//when SomethingHappened fires.
void HandleSomethingHappened(string foo)
{
    //Do some stuff
}

//I am creating a delegate (pointer) to HandleSomethingHappened
//and adding it to SomethingHappened's list of "Event Handlers".
myObj.SomethingHappened += new MyEventHandler(HandleSomethingHappened);

//To raise the event within a method.
SomethingHappened("bar");

(*This is the key to events in .NET and peels away the "magic" - an event is really, under the covers, just a list of methods of the same "shape". The list is stored where the event lives. When the event is "raised", it's really just "go through this list of methods and call each one, using these values as the parameters". Assigning an event handler is just a prettier, easier way of adding your method to this list of methods to be called).

Tuesday, June 1, 2021
 
njai
answered 7 Months ago
56

With <out T>, you can treat the interface reference as one upwards in the hierarchy.

With <in T>, you can treat the interface reference as one downwards in the hiearchy.

Let me try to explain it in more english terms.

Let's say you are retrieving a list of animals from your zoo, and you intend to process them. All animals (in your zoo) have a name, and a unique ID. Some animals are mammals, some are reptiles, some are amphibians, some are fish, etc. but they're all animals.

So, with your list of animals (which contains animals of different types), you can say that all the animals have a name, so obviously it would be safe to get the name of all the animals.

However, what if you have a list of fishes only, but need to treat them like animals, does that work? Intuitively, it should work, but in C# 3.0 and before, this piece of code will not compile:

IEnumerable<Animal> animals = GetFishes(); // returns IEnumerable<Fish>

The reason for this is that the compiler doesn't "know" what you intend, or can, do with the animals collection after you've retrieved it. For all it knows, there could be a way through IEnumerable<T> to put an object back into the list, and that would potentially allow you to put an animal that isn't a fish, into a collection that is supposed to contain only fish.

In other words, the compiler cannot guarantee that this is not allowed:

animals.Add(new Mammal("Zebra"));

So the compiler just outright refuses to compile your code. This is covariance.

Let's look at contravariance.

Since our zoo can handle all animals, it can certainly handle fish, so let's try to add some fish to our zoo.

In C# 3.0 and before, this does not compile:

List<Fish> fishes = GetAccessToFishes(); // for some reason, returns List<Animal>
fishes.Add(new Fish("Guppy"));

Here, the compiler could allow this piece of code, even though the method returns List<Animal> simply because all fishes are animals, so if we just changed the types to this:

List<Animal> fishes = GetAccessToFishes();
fishes.Add(new Fish("Guppy"));

Then it would work, but the compiler cannot determine that you're not trying to do this:

List<Fish> fishes = GetAccessToFishes(); // for some reason, returns List<Animal>
Fish firstFist = fishes[0];

Since the list is actually a list of animals, this is not allowed.

So contra- and co-variance is how you treat object references and what you're allowed to do with them.

The in and out keywords in C# 4.0 specifically marks the interface as one or the other. With in, you're allowed to place the generic type (usually T) in input-positions, which means method arguments, and write-only properties.

With out, you're allowed to place the generic type in output-positions, which is method return values, read-only properties, and out method parameters.

This will allow you to do what intended to do with the code:

IEnumerable<Animal> animals = GetFishes(); // returns IEnumerable<Fish>
// since we can only get animals *out* of the collection, every fish is an animal
// so this is safe

List<T> has both in- and out-directions on T, so it is neither co-variant nor contra-variant, but an interface that allowed you to add objects, like this:

interface IWriteOnlyList<in T>
{
    void Add(T value);
}

would allow you to do this:

IWriteOnlyList<Fish> fishes = GetWriteAccessToAnimals(); // still returns
                                                            IWriteOnlyList<Animal>
fishes.Add(new Fish("Guppy")); <-- this is now safe

Here's a few videos that shows the concepts:

  • Covariance and Contravariance - VS2010 C# Part 1 of 3
  • Covariance and Contravariance - VS2010 C# Part 2 of 3
  • Covariance and Contravariance - VS2010 C# Part 3 of 3

Here's an example:

namespace SO2719954
{
    class Base { }
    class Descendant : Base { }

    interface IBibbleOut<out T> { }
    interface IBibbleIn<in T> { }

    class Program
    {
        static void Main(string[] args)
        {
            // We can do this since every Descendant is also a Base
            // and there is no chance we can put Base objects into
            // the returned object, since T is "out"
            // We can not, however, put Base objects into b, since all
            // Base objects might not be Descendant.
            IBibbleOut<Base> b = GetOutDescendant();

            // We can do this since every Descendant is also a Base
            // and we can now put Descendant objects into Base
            // We can not, however, retrieve Descendant objects out
            // of d, since all Base objects might not be Descendant
            IBibbleIn<Descendant> d = GetInBase();
        }

        static IBibbleOut<Descendant> GetOutDescendant()
        {
            return null;
        }

        static IBibbleIn<Base> GetInBase()
        {
            return null;
        }
    }
}

Without these marks, the following could compile:

public List<Descendant> GetDescendants() ...
List<Base> bases = GetDescendants();
bases.Add(new Base()); <-- uh-oh, we try to add a Base to a Descendant

or this:

public List<Base> GetBases() ...
List<Descendant> descendants = GetBases(); <-- uh-oh, we try to treat all Bases
                                               as Descendants
Tuesday, June 1, 2021
 
Yrtymd
answered 7 Months ago
74

You can't, basically - at least not without reflection and a lot of grubbiness.

Events are strictly "subscribe, unsubscribe" - you can't unsubscribe someone else's handler, any more than you can change someone else's reference to an object.

Thursday, June 10, 2021
 
lewiguez
answered 6 Months ago
71

It will still work with events that have an explicit add/remove - you just need to use the delegate variable (or however you've stored the delegate) instead of the event name.

However, there's an easier way to make it thread-safe - initialize it with a no-op handler:

public event EventHandler SomethingHappened = delegate {};

The performance hit of calling an extra delegate will be negligible, and it sure makes the code easier.

By the way, in your extension method you don't need an extra local variable - you could just do:

static public void RaiseEvent(this EventHandler @event, object sender, EventArgs e)
{
    if (@event != null)
        @event(sender, e);
}

static public void RaiseEvent<T>(this EventHandler<T> @event, object sender, T e)
    where T : EventArgs
{
    if (@event != null)
        @event(sender, e);
}

Personally I wouldn't use a keyword as a parameter name, but it doesn't really change the calling side at all, so do what you want :)

EDIT: As for the "OnXXX" method: are you planning on your classes being derived from? In my view, most classes should be sealed. If you do, do you want those derived classes to be able to raise the event? If the answer to either of these questions is "no" then don't bother. If the answer to both is "yes" then do :)

Monday, June 28, 2021
 
drowneath
answered 6 Months ago
77

"...does Java just handle this polling loop in the background for us...?"

Pretty much, yes. The UI system provides the program with access to a queue that contains events that have occurred and the target. The program runs through a loop requesting items from this queue and then does whatever. Libraries like Qt and Java call special functions within the widget classes that tell them an event has happened so that the system can function from there, however stipulated by the API. The library has to translate from the system specific window ID to the class governing that widget in order to do so.

Qt provides access to this function in the form of protected, virtual onXxxxEvent() functions. The standard behavior of many widgets is to generate signals in response to events, but these are always some translated form of the event specific to the particular widget. Qt provides you access to this so that you can override the way a widget handles an event or to add additional behavior for events it never listened to before (through subclassing).

Each UI system is slightly different but they are all basically the same in this manner in my experience. We're talking raw win32 and Xlib here.

Thursday, September 16, 2021
 
StevenR
answered 3 Months ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :
 
Share