Back in Part 1, we covered the history of Delegates in the .NET Framework. As stated, in 1.1, they were a little too confined to be exceptionally useful. However, in the 2.0 Framework, Delegates were given the ability to be "Anonymous" and could be created on the fly. This allows for more elegant coding, and serves as an introduction to some concepts previously relegated to Functional Programming.
First, an example. In the previous post, this was our basic demonstration of a delegate:
public delegate void SomeFunction(int num);
public void PrintHello(int num) {
for (int i = 0; i < num; i++) {
Console.WriteLine("Hello.");
}
}
public void Main() {
SomeFunction func = new SomeFunction(this.PrintHello);
func(10);
}
Anonymous Methods mean that we do not have to define the PrintHello method as a part of the class, it can be created inline:
public delegate void SomeFunction(int num);
public void Main() {
SomeFunction func = delegate(int num) {
for (int i = 0;, i < num; i++) {
Console.WriteLine("Hello.");
}
};
func(10);
}
This in and of itself is useful; it cuts down a few lines of code, but doesn't necessarily add too much value.
Enter Closures. A Closure is "a [method] that is evaluated in an environment containing one or more bound variables." (I'll use OO terminology since this is C# related). What this means is that an anonymous method can refer and use variables defined in the scope surrounding it. Thus:
public delegate void SomeFunction(int num);
public void Main() {
string s = "This is what will print.";
SomeFunction func = delegate(int num) {
for (int i = 0;, i < num; i++) {
Console.WriteLine(s); // not defined in the delegate, but still valid
}
};
func(10);
}
This of course begs the question of the ages, so what? Why would I possibly want to do something like that? Well, for a simple example, let's say you have a collection of items, say, the items in a ListView, and you have a bunch of operations you want to perform on them at different times. The non-delegate way would be to have separate methods that iterate through the list and do its thing.
public void HighliteEven() {
for (int i = 0; i < this.listView1.Items.Count; i++) {
ListViewItem li = this.listView1.Items[i];
if (i % 2 == 0) {
li.BackColor = Color.Red;
}
}
}
public List<ListViewItem> ExtractLong() {
List<ListViewItem> items = new List<ListViewItem>();
foreach (ListViewItem item in this.listView1.Items) {
if (item.Text.Length > 50) {
items.Add(item);
this.listView1.Items.Remove(item);
}
}
return items;
}
These are only two methods, but it's not uncommon to have upwards of a dozen, depending on how robust the UI is. If you have an ExtractLong, there's a good chance you'll need an ExtractShort, and probably more ExtractX methods too. Already you can see code duplication in the loops. Anonymous methods let you refactor this code to:
private delegate void ItemAction(ListViewItem item, int index);
private void ActOnListView(ItemAction action) {
for (int i = 0; i < this.listView1.Items.Count; i++) {
ListViewItem item = this.listView1.Items[i];
action(item, i);
}
}
public void HighliteEven() {
this.ActOnListView(delegate(ListViewItem item, int index) {
if (index % 2 == 0) {
item.BackColor = Color.Red;
}
});
}
public List<ListViewItem> ExtractLong() {
List<ListViewItem> items = new List<ListViewItem>();
this.ActOnListView(delegate(ListViewItem item, int index) {
if (item.Text.Length > 50) {
items.Add(item);
this.listView1.Items.Remove(item);
}
});
return items;
}
This is better, but we can refactor the Extract method even more. .NET 2.0 also introduces a special delegate called Predicate<T>. If you have used the Array.Find method before, you've used Predicate<T>. A predicate simply executes an expression that returns a boolean. In Array.Find()'s case, it evaluates the object at each index of the array. If the object passes the test provided, it adds it to the return set.
We can use the same idea here to create a generic Extract method which we can give any number of comparers, greatly shortening each ExtractX method.
private List<ListViewItem> Extract(Predicate<ListViewItem> comparer) {
List<ListViewItem> items = new List<ListViewItem>();
this.ActOnListView(delegate(ListViewItem item, int index) {
if (comparer(item)) {
items.Add(item);
this.listView1.Items.Remove(item);
}
});
return items;
}
public List<ListViewItem> ExtractLong() {
return this.Extract(delegate(ListViewItem item) {
return item.Text.Length > 50;
});
}
Making an ExtractShort method is just another 3 liner, who's payload is return item.Text.Length < 5; So, rather than having 8 lines of code (at least) per Extract method, you can have 3, which can be a huge savings, and makes testing far easier.
These concepts, and the syntax, may look a little intimidating, but once you have used them for a little bit, you won't want to go back. In the next post, I'll cover the updates to .NET 3.5, and show similar features in a couple other languages.
Be the first to rate this post
- Currently 0/5 Stars.
- 1
- 2
- 3
- 4
- 5