Delegates are a big part of the .NET Framework. They were included since the beginning with .NET 1.0 and have matured since then, but in my experience I don't see them used intentionally too often.
I think the reason for this is because the concept of a delegate is kind of foreign if you have a more procedural background, using mainly languages like Visual Basic or Java, and to a certain extent, C. I certainly hadn't encountered anything like that until I started using C#, and had a difficult time wrapping my head around the concept.
To understand them better, let's take a look at the history of delegates, starting with the positively ancient and crusty .NET 1.0.
At the most basic level, a delegate serves as a way to treat a method as a variable. This means that it can be passed around to different methods, or have its execution delayed until deemed fit. A common reaction to this is "Why would I ever want to do that?" Well, it lets you do some interesting things. For starters, it makes it easier to perform certain kinds of logic at runtime without having to write a great deal of code.
And since every explanation is better with a little bit of code, let's get to it. As stated, delegates have been around for a while. With .NET 1.0, a delegate could only wrap an existing method, nothing on-the-fly:
// This is the definition of the delegate. The return type (void) and
// arguments (int num) are the signature for any methods that
// will be wrapped by this delegate.
public delegate void SomeFunction(int num);
// A standard method
public void PrintHello(int num) {
for (int i = 0; i < num; i++) {
Console.WriteLine("Hello.");
}
}
public void Main() {
// PrintHello's signature matches what was declared by the
// delegate, so it can be used as a variable if needed.
SomeFunction func = new SomeFunction(this.PrintHello);
func(10);
}
Again, why does this even matter? The most useful thing about delegates is that you don't need to know which method needs to be called at compile time. One of the classical Design Patterns is the Command Pattern, which let's you abstract a common piece of functionality out to a variable, and pass it around... sound familiar? Delegates are not a complete substitute for the pattern, but can perform similar functionality with far less code. And less code is always better.
Additionally, .NET's event model is based on delegates. The EventHandler is really just a delegate that returns null and takes an Object and an EventArgs. They are also used with some of the asynchronous logic in the framework, BeginInvoke being primary.
For a real-world example, I've seen them used as a neat solution for some of the shortcomings with a Windows Form ToolBar. Back in .NET 1.1, the ToolBar still had many COM underpinnings, and the ToolBarButtons that made up the Bar did not have individual OnClick events on them; you had to handle the ToolBar's ButtonClick event, and pull the Button property from the event's arguments (this was a mess that was replaced with the ToolStrip in .NET 2.0). Once you got this, then you would need to perform some sort of test to determine the appropriate action method. Rather than have a 20 line select statement, one bright co-worker just wrapped the desired method in a delegate and dropped it into the button's Tag property. In just two lines he was able to accomplish what my 20+ did. I call that a bargain.
Here is an example of using delegates to implement a State Machine. This is just the framework for a machine, the strength of it lies in its ability to declare the edges of the graph dynamically.
public delegate Node EvalOp(State s);
public abstract class State {
// State contains all information about the current position in the graph.
}
public class Node {
private string _name;
public EvalOp[] ops;
public Node(string name) {
this._name = name;
this.ops = new EvalOp[] { };
}
public string Name {
get { return this._name; }
}
public Node Evaluate(State s) {
foreach (EvalOp op in this.ops) {
Node n = op(s);
if (n != null) {
return n;
}
}
// If we reach this point, we are at the end.
return null;
}
private static Node _end = new Node("End");
public static Node EndNode {
get { return _end; }
}
}
public abstract class Graph {
protected Node[] nodes;
protected Node currentNode;
protected State state;
public Graph() {
this.Init();
}
public void Start() {
for (this.currentNode = nodes[0]; this.currentNode != Node.EndNode; this.currentNode = this.currentNode.Evaluate(this.state)) {
this.Step();
}
}
protected abstract void Step();
// Create nodes and state here
protected abstract void Init();
}
The basic operation is that each node contains an array of delegates (EvalOp) to define the edges to a new node. The EvalOp takes a State object and determines if it satisfies the requirements of the edge. If it does, it returns the next node to travel to. Each EvalOp is evaluated until the first valid Node is returned. It's not the most robust State Machine ever created, but I wanted to demonstrate delegates rather than State Machines.
I wrote a simple implementation to demonstrate it. There are three nodes, including the EndNode already created by the framework. One is called Odd, and the other is Even, and they switch back and forth depending on the value of State, which is incremented each step of the way, until 100 is reached.
public class ConcreteState : State {
private int _count;
// Note the lack of auto-properties in 1.0. Argh.
public int Count {
get { return this._count; }
set { this._count = value; }
}
}
public class ConcreteGraph : Graph {
protected override void Init() {
this.state = new ConcreteState();
Node odd = new Node("Odd");
Node even = new Node("Even");
EvalOp oddTest = new EvalOp(this.OddOp);
EvalOp evenTest = new EvalOp(this.EvenOp);
EvalOp finishTest = new EvalOp(this.CountOp);
odd.ops = new EvalOp[] { finishTest, oddTest };
even.ops = new EvalOp[] { finishTest, evenTest };
this.nodes = new Node[] { odd, even };
}
protected override void Step() {
((ConcreteState)this.state).Count++;
}
private Node OddOp(State s) {
if (((ConcreteState)s).Count % 2 == 1) {
Console.WriteLine("Odd");
}
return this.nodes[1]; // move to the even op
}
private Node EvenOp(State s) {
if (((ConcreteState)s).Count % 2 == 0) {
Console.WriteLine("Even");
}
return this.nodes[0]; // move to the odd op.
}
private Node CountOp(State s) {
return ((ConcreteState)s).Count == 100 ? Node.EndNode : null;
}
}
More complicated functionality could be added by just creating a new EvalOp delegate (like say, MultipleOfFourOp) and adding it to the proper node[s].
Delegates are not all roses of course. Their biggest shortcoming in 1.1, was that they were too rigid. You needed to have a method defined elsewhere in the app, and you couldn't do some of the neat things that other languages were able to do with similar constructs (typically called lambdas). Fortunately, .NET 2.0 significantly improved delegate's abilities and 3.5 made them extra delicious. I'll write more about them later.
Be the first to rate this post
- Currently 0/5 Stars.
- 1
- 2
- 3
- 4
- 5