Delegates
I'll start with a string replacement problem, which I shall borrow from a question posted to the asp.net forums which concerned the Regex.Replace() methods. The overloaded method I am particularly interested in is the one that takes (string, MatchEvaluator). The final argument - MatchEvaluator - is a Delegate, and it represents the method that will be called each time a match is found during a Replace operation. The problem that needed to be solved was that the questioner needed to replace multiple instances of
!!-directory/target.aspx-!!
with
<a href="~/directory/target.aspx">TARGET</a>
I know - you're thinking "That's great! He's going to use something really complicated, like Regular Expressions to explain something else that's confusing!!". However, Delegates aren't generally used in run-of-the-mill C#, but this is one place (among others, including LINQ) where they do actually feature, so it's a lot more meaningful showing how they are applied here, than concocting some kind of scenario featuring cars or thermostatic boilers. Also, Regular Expressions are actually easy. It's just getting the right pattern that can require some work. To highlight this, here's a problem statement that clarifies what we are going to do:
1. Take a string as an input
2. Match all occurrences of !!-directory/target-!!, where directory and target may change,
3. Replace them with formatted URLs
Finding and matching the pattern !!-directory/target-!! is pretty straightforward and requires a very simple Regex pattern:
Regex r = new Regex("!!-(.*?)/(.*?)-!!");
A Regular Expression Match object represents a single match that is found using the pattern. The Match object also has a Groups property which holds a collection. There is always at least one item in the collection - referenced by its indexer 0, which is the actual matching string. Additional groups are defined by the parentheses, and will hold the directory element and the target element. These will be referenced by their indexers - 1 and 2. Just to explain the actual pattern, it looks to match an item that follows this structure:
Starts with !!- as a literal string
Followed by anything - .*? is a Regular Expression wildcard that says "match any character except a newline character any number of times". This will actually capture whatever directory represents
Followed by a forward slash /
Followed by anything again, which will match whatever target represents
Followed by -!! as a literal string
What then has to happen is some further processing on the actual match to produce a replacement for the match that is formatted as a URL. The actual implementation of the processing is task-specific, so it hasn't been written yet. And that, fundamentally is what a Delegate is: it is a type (which means it can be passed in as a parameter to a method) that references or "points to" a method. The MatchEvaluator delegate is defined in the documentation thus:
public delegate string MatchEvaluator(
Match match
)
This means that the delegate must point to a method that takes a Match object as a parameter, and it must return a string. The actual method body can do anything it likes, so long as it complies with these two requirements. In the case of the current requirement, the method that the MatchEvaluator delegate will point to is as follows:
public string CreateURLS(Match m)
{
return String.Format("<a href=\"~/{0}/{1}\">{2}</a>",
m.Groups[1].Value,
m.Groups[2].Value,
m.Groups[2].Value.Replace(".aspx", "").ToUpper());
}
It's a relatively straightforward method that accepts a Match object and returns a formatted string, so it follows the rules defined by the Delegate. If I put the whole code in a Code-Behind page, it will look like this:
using System;
using System.Text.RegularExpressions;
public partial class Delegates : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
//A string to run the Regular Expression over
string input = @"!!-products/drives.aspx-!! !!-main/contact.aspx-!!";
//Define the pattern for the Regex object
Regex r = new Regex("!!-(.*?)/(.*?)-!!");
//Declare the MatchEvaluator delegate and point it to a valid method
MatchEvaluator me = new MatchEvaluator(CreateURLS);
//Consign the result of the Regex operation to a string variable
string output = r.Replace(input, me);
//output returns <a href="~/products/drives.aspx">DRIVES</a> <a href="~/main/contact.aspx">CONTACT</a>
}
//The method that the MatchEvaluator Delegate will point to
public string CreateURLS(Match m)
{
return String.Format("<a href=\"~/{0}/{1}\">{2}</a>",
m.Groups[1].Value,
m.Groups[2].Value,
m.Groups[2].Value.Replace(".aspx", "").ToUpper());
}
}
Anonymous Methods
Delegates have been around since C# was first released. With C# 2.0, Anonymous Methods were introduced. Essentially, Anonymous Methods provide a way to pass the method body that appeared in the previous sample as CreateURLS() as a parameter to the Delegate, rather than the name of the method it should point to. The main benefit of this is that it makes for more concise code. Now we'll re-write the previous sample using Anonymous Methods:
using System;
using System.Text.RegularExpressions;
public partial class Delegates : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
string input = @"!!-products/drives.aspx-!! !!-main/contact.aspx-!!";
Regex r = new Regex("!!-(.*?)/(.*?)-!!");
string output = r.Replace(input, delegate(Match m)
{
return String.Format("<a href=\"~/{0}/{1}\">{2}</a>",
m.Groups[1].Value,
m.Groups[2].Value,
m.Groups[2].Value.Replace(".aspx", "").ToUpper());
});
//output returns <a href="~/products/drives.aspx">DRIVES</a> <a href="~/main/contact.aspx">CONTACT</a>
}
}
This is functionally equivalent, but is a more concise - even elegant - way of writing code in that it allows the method that the delegate points at to be added "inline", and doesn't require that a formal method be created and named.
Lambda Expressions
Lambda Expressions, using the Lambda Operator => were introduced in C# 3.0. While Anonymous Methods were a new feature in 2.0, Lambda Expressions are simply an improvement to syntax when using Anonymous Methods. There is no longer a need to use the delegate keyword, or provide the type of the parameter. The type can usually be inferred by the compiler from usage, and in the case of the code below, hovering over m in Code View will show m is of type Match. Where the compiler can't do this, you would change the code to read (Match m) =>.
using System;
using System.Text.RegularExpressions;
public partial class Delegates : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
string input = @"!!-products/drives.aspx-!! !!-main/contact.aspx-!!";
Regex r = new Regex("!!-(.*?)/(.*?)-!!");
string output = r.Replace(input,
m => String.Format("<a href=\"~/{0}/{1}\">{2}</a>",
m.Groups[1].Value,
m.Groups[2].Value,
m.Groups[2].Value.Replace(".aspx", "").ToUpper()));
//output returns <a href="~/products/drives.aspx">DRIVES</a> <a href="~/main/contact.aspx">CONTACT</a>
}
}
Basically, the code is identical to the Anonymous Methods example. What the => operator effectively does it to say: take the parameter on the left, then do whatever the code on the right says to it.
UPDATE: Following on from another question in the asp.net forums, here is an additional example of a Lambda expression in use with the Regex.Replace(string, MatchEvaluator) method. This example is likely to be of more generic use to readers, as it takes links in a block of text and formats them as HTML hyperlinks. Where the delegate comes in is if the anchor text displayed on the page exceeds a certain number of characters (30 in this example), the text is trimmed and appended with elipses (...). The person who posted the question had a fixed width div or column in which to display links, and didn't want them overflowing the width.
public static string ReplaceLinks(string arg)
//Replaces web and email addresses in text with hyperlinks
{
Regex urlregex = new Regex(@"(^|[\n ])((www|ftp)\.[^ ,""\s<]*)");
arg = urlregex.Replace(arg,
m => String.Format(" <a href=\"http://{0}\">{1}</a> ",
m.Groups[2].Value,
m.Groups[2].Value.Length > 27 ? m.Groups[2].Value.Substring(0,27) + "..." : m.Groups[2].Value));
Regex httpurlregex = new Regex(@"(^|[\n ])((http://www\.|http://|https://)[^ ,""\s<]*)");
arg = httpurlregex.Replace(arg,
m => String.Format(" <a href=\"{0}\">{1}</a> ",
m.Groups[2].Value,
m.Groups[2].Value.Length > 27 ? m.Groups[2].Value.Substring(0, 27) + "..." : m.Groups[2].Value));
Regex emailregex = new Regex(@"([\w_.-]+\@[\w_.-]+\.\w+\s)");
arg = emailregex.Replace(arg,
m => String.Format(" <a href=\"mailto:{0}\">{1}</a> ",
m.Groups[1].Value,
m.Groups[1].Value.Length > 27 ? m.Groups[1].Value.Substring(0, 27) + "..." : m.Groups[1].Value));
return arg;
}
You can see the benefits that Lambda expressions bring to coding even more clearly with the following exercise that uses LINQ.
using System;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using System.Linq;
public partial class Delegates : System.Web.UI.Page
{
public class Car
{
public string Model { get; set; }
public string Make { get; set; }
public int Doors { get; set; }
}
protected void Page_Load(object sender, EventArgs e)
{
List<Car> cars = new List<Car>()
{
new Car {Model = "Ford", Make = "Focus", Doors = 5},
new Car {Model = "Ford", Make = "Fiesta", Doors = 3},
new Car {Model = "Audi", Make = "A4", Doors = 4},
new Car {Model = "TVR", Make = "Chimera", Doors = 2},
new Car {Model = "Honda", Make = "Civic", Doors = 5},
new Car {Model = "Volvo", Make = "S60", Doors = 4}
};
IEnumerable<Car> result = cars.Where(c => c.Doors == 5);
}
}
This code declares a class called Car, and then adds some properties using the new C# 3.0 Automatic Properties feature, then creates a generic list of Cars, using C# 3.0's shorthand Collection Initializers. Finally, one line of code gets the cars within the generic list that have 5 doors. If this were written using an Anonymous Method instead, the final line would be replaced with:
IEnumerable<Car> result = cars.Where(
delegate(Car c)
{
return c.Doors == 5;
});