In my previous entry on this topic I demonstrated how you can use Extension methods to add what appear to be new methods to existing classes. In this entry I will demonstrate how Extension methods enhance Linq by adding “method queries” using lambda expressions.
To see how this works, let’s set up a simple query, with a display that will show the two ways we might execute that query using Linq.
We’ll start by creating a new Silverlight application and within the application a simple data class, Person. Here is the code for Person.cs
using System.Collections.Generic; namespace ExtensionPart2 { public class Person { public string FirstName { get; set; } public string LastName { get; set; } public string EmailAddress { get; set; } public override string ToString() { return string.Format( "{0} {1}\n{2}", FirstName, LastName, EmailAddress ); } public static List<Person> GetPeople() { List<Person> people = new List<Person>(); people.Add( new Person() { FirstName = "Jesse", LastName = "Liberty", EmailAddress = "[email protected]" } ); people.Add( new Person() { FirstName = "George", LastName = "Washington", EmailAddress = "[email protected]" } );
// also added John Adams, Thomas Jefferson
// James Madison, James Monroe, John Q. Adams return people; } } }
(I’ve elided the initialization of 5 of the presidents to save room)
We will issue two queries against this data. The first is a traditional Linq query,
from person in people where person.LastName.StartsWith( "M" ) select person;
While this works fine, taking advantage of extension methods allows us (or more accurately the authors of the library) to extend the List<t> class to add the Where method, which in turn allows us to collapse these three statements to a single line of code using a lambda expression,
people.Where( person => person.LastName.StartsWith( "M" ) );
Examining the Extension Method
The tooltip for people.Where give great insight into what is actually going on here
Taking this apart, we see that Where is defined to be an extension method that returns an IEnumerable<Person> and takes one argument: a function (named predicate) that takes two arguments: a Person object and a boolean.
Rather than passing in a method, or a delegate to a method, or even an anonymous method, we go one step further and pass in a lambda expression. As you know from previous articles, the lambda expression “are a way to write short in-line substitutions for the methods that delegates refer to.”
You can read the lambda expression above as
“There is a method that takes one parameter, a list of person objects named people and that returns each person whose last name starts with the letter m in an IEnumberable collection.”
or, more conventionally you can read it as
“person goes to each person whose last name starts with m”
For a lengthy review of how to read this aloud see this article. |
The result for both queries is identical, but I would argue the second query is far more readable, as shown in the running program,
The code for Page.xaml is shown here, and below it the code for Page.xaml.cs
Page.xaml
<UserControl x:Class="ExtensionPart2.Page" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" HorizontalAlignment="Left" VerticalAlignment="Center" FontFamily="Georgia" FontSize="14" Width="650" Height="553" Margin="5"> <Grid x:Name="LayoutRoot" Background="Bisque" Margin="20"> <Grid.RowDefinitions> <RowDefinition Height="1*" /> <RowDefinition Height="5*" /> <RowDefinition Height="3*" /> <RowDefinition Height="3*" /> <RowDefinition Height="1.5*" /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="6*" /> <ColumnDefinition Width="4.5*" /> </Grid.ColumnDefinitions> <TextBlock Height="Auto" Width="Auto" Grid.Row="0" Text="Where last name begins with M" TextWrapping="Wrap" FontFamily="Georgia" FontSize="24" Margin="0" Grid.RowSpan="1" Grid.ColumnSpan="2" HorizontalAlignment="Center" /> <TextBlock Text="Presidents" TextWrapping="NoWrap" FontFamily="Georgia" FontSize="18" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="5,5,5,5" d:LayoutOverrides="Height" Grid.Row="1" /> <ListBox x:Name="PresidentsListBox" Margin="5" Grid.Row="1" Grid.Column="1" /> <TextBlock HorizontalAlignment="Left" VerticalAlignment="Bottom" Margin="5,4,5,5" Grid.Row="2" FontFamily="Georgia" FontSize="14" Text="" TextWrapping="Wrap" /> <ListBox x:Name="LinqResults" Margin="5" Grid.Row="2" Grid.RowSpan="1" Grid.Column="1" /> <ListBox x:Name="QOEM_Results" Margin="5" Grid.Column="1" Grid.Row="3" Grid.RowSpan="1" /> <StackPanel Margin="0" Grid.Row="2"> <TextBlock Height="Auto" Width="Auto" RenderTransformOrigin="0.5,0.5" FontFamily="Georgia" FontSize="18" Text="from person in people" TextWrapping="NoWrap" HorizontalAlignment="Left" /> <TextBlock Height="Auto" RenderTransformOrigin="0.5,0.5" FontFamily="Georgia" FontSize="18" Text="where person.LastName.StartsWith ("M")" TextWrapping="NoWrap" Width="Auto" HorizontalAlignment="Left" /> <TextBlock RenderTransformOrigin="0.5,0.5" FontFamily="Georgia" FontSize="18" Text="select person" TextWrapping="NoWrap" Width="334" Height="20" HorizontalAlignment="Left" /> </StackPanel> <StackPanel Margin="0,0,0,0" Grid.Row="3"> <TextBlock x:Name="QOEM" FontFamily="Georgia" FontSize="18" Text="people.Where ( person => person.LastName.StartsWith ( "M" );" TextWrapping="Wrap"/> </StackPanel> <Button x:Name="Go" Background="#FF00FF00" FontFamily="Georgia" FontSize="24" Foreground="#FF0000FF" Margin="5" Grid.Column="1" Grid.Row="5" Content="Go!" /> </Grid> </UserControl>
Page.xaml.cs
using System.Collections.Generic; using System.Linq; using System.Windows; using System.Windows.Controls; namespace ExtensionPart2 { public partial class Page : UserControl { public Page() { InitializeComponent(); Go.Click += new RoutedEventHandler( Go_Click ); } void Go_Click( object sender, RoutedEventArgs e ) { List<Person> people = Person.GetPeople(); // bind entire collection PresidentsListBox.ItemsSource = people; // traditional linq statement LinqResults.ItemsSource = from person in people where person.LastName.StartsWith( "M" ) select person; // extended method QOEM_Results.ItemsSource = people.Where(
person => person.LastName.StartsWith( "M" ) ); } // end go button event handler } // end class } // end namespace