1.1 Event Handlers
Handling events is probably the simplest way of working with
events. (There is a fair amount of
infrastructure that is not clear until you implement an event, but I will get to
that shortly).
using System;
using System.Windows.Forms;
class MyForm:Form{
MyForm(){
Button button = new
Button();
button.Text =
"Button";
button.Click += new
EventHandler(HandleClick);
Controls.Add(button);
}
void HandleClick(Object sender, EventArgs
e){
MessageBox.Show("The
button s Click event has been raised!");
}
public static void
Main(){
Application.Run(new
MyForm());
}
}
Figure 1‑1
EventHand.cs
The code in Figure 1‑1 shows a simple event
registration. In the constructor of
MyForm a Button instance
is created and added to the form’s controls.
Before this is done, MyForm registers its
interest in the button’s Click event. This is done by adding a new instance of the
EventHandler delegate to the Click event as shown in red. (A delegate is a managed way of
encapsulating a callback function. We
will discus delegates more shortly).
The handler function method is called HandleClick() and is shown in red as well. This method is called each time a user clicks
on the button and causes the Click event to be
fired.
Here are a couple of points about this code.
·
Notice that the += syntax is
used to register your interest in an event.
This is used because it is possible for more than one method to be
registered and called when an event is fired.
(To unregister a handler use the -=
operator).
·
The parameter list and return type of the HandleClick() method must match the defined parameter list
and return type for the EventHandler delegate. The EventHandler
delegate is a type defined by the FCL.
·
The Click event is defined as a
member of the Button type in the FCL. When you define events for your own type’s
you may use any delegate you like for the callback method, or you can create
your own delegate. Remember that the
delegate defines the signature of the callback method.
If you are creating a type that needs to allow code to
register its interest in an event then you must decide what information you want
to pass to the handler methods when the event is raise. This will affect your choice of delegate
type.
The following example defines a delegate and an event.
class EventInt{
Int32 val;
public Int32
Value{
get{return
val;}
set{
if(Changed != null)
Changed(value, val);
val = value;
}
}
public event Callback Changed;
public delegate void Callback(Int32 newVal, Int32
oldVal);
}
Figure 1‑2 EventInt.cs
The EventInt.cs example is a
simple (and most likely useless) type that shows the definition of an event. The EventInt type implements an Int32 value through a property, and the property fire’s a
Changed event if the
value is changed. The firing code is
shown in red.
The type defines its own delegate
(which is a nested type) named Callback. The Callback
delegate indicates that a handling method must
return void and take two Int32 parameters.
The EventInt type also defines an
event named Changed
which is based on the Callback delegate. To
register your interest in this event you would use the EventInt type in much the same way as the Button type was used in Figure 1‑1.
class App{
public static void
Main(){
EventInt num =
new EventInt();
num.Changed += new
EventInt.Callback(HandleChange);
num.Value =
20;
num.Value += 10;
}
static void
HandleChange(Int32 newVal, Int32 oldVal){
Console.WriteLine("Changed from {0} to {1}", oldVal, newVal); }
}
Figure 1‑3
EventInt consumer
The code in Figure 1‑3 creates an instance of EventInt, and then registers a handler. Notice that the HandleChange() method matches the definition of the Callback delegate in Figure 1‑2.
In this code example, the HandleChange()
method is called twice.
Events are very common in managed code, but sometimes you
will only need to use a callback. In
this case you can use a delegate, without an
event. Delegates are special types that
are defined with special syntax that looks almost like a function
definition.
public
delegate void Callback(Int32 newVal,
Int32 oldVal);
The preceding code defines a type named Callback that can be
used to represent a method to be called back.
The interesting thing about delegates is that you can chain
them. This means that you can register
more than one callback and fire it with what looks like a single method call.
using System;
delegate void MyDelegate(String message);
class App{
public static void
Main(){
MyDelegate call = new
MyDelegate(FirstMethod);
call += new
MyDelegate(SecondMethod);
call("Message
A");
call("Message
B");
call("Message
C");
}
static void
FirstMethod(String str){
Console.WriteLine("1st method: "+str);
}
static void
SecondMethod(String str){
Console.WriteLine("2nd method: "+str);
}
}
Figure 1‑4
Delegates.cs
In this example a delegate named
MyDelegate is defined, and two instances are
created, one-each for the methods FirstMethod and
SecondMethod.
Then the delegate chain is called back three
times with three different messages.
Interfaces are a great feature of object oriented
programming. Some languages such as C++
support a concept called multiple derivation.
The .NET Framework does not allow for multiple inheritance, and as a
result neither does C#. Sometimes, it is
nice, however, for a type to be able to take on several roles. This is where Interfaces come in.
A type is what it is and it is also what its base class
is. But in programming sometimes it is
nice to have a well defined role that a type can fulfill, without the role being
defined as a base class. Here is an
example.
using System;
class SomeType{};
class SortType:SomeType, IComparable{
Int32
val;
public SortType(Int32
val){
this.val =
val;
}
public Int32 CompareTo(Object obj){
return this.val -
((SortType)obj).val;
}
public override string
ToString(){
return
val.ToString();
}
}
class App{
public static void
Main(){
SomeType[] objs = new
SomeType[]{
new SortType(3),
new SortType(1), new SortType(2)};
Array.Sort(objs);
foreach(SomeType o in
objs){
Console.WriteLine(o.ToString());
}
}
}
Figure 2‑1
Sortable.cs
In the Figure 2‑1 a type SortType is derived from a type SomeType. Even
though each instance of SortType continues to also
be a SomeType, they have the additional
functionality of having implemented the IComparable
interface’s CompareTo() method.
It’s this interface that the Sort() method of Array looks
for when trying to sort the array of objects.
Inside of the array method, each object of the array is being cast to a
reference variable of type IComparable, and then the
CompareTo() method is being called, like so.
IComparable
ref = (IComparable) obj;
Ref.CompareTo(otherObj);
This is how any object becomes an IComparable just long enough to compare itself (although
the instance isn’t really changing at all, but the code’s view of the instance
has changed). Interfaces are a way for
objects to have flexible roles while maintaining a strict type.