5.5. LINQ to IEnumerable
One of the elegant design aspects of LINQ is that queries can be executed against any enumerable data source. If an object implements
IEnumerable
, then LINQ can access the data behind that object. For example, suppose we need to search the current
user
's
My Documents
folder (and sub-folders) for all
non-system
files modified in the last
hour
. Using LINQ we do this as
follows
:
using SIO = System.IO;
string[] files;
string mydocs;
mydocs
= Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
files
= SIO.Directory.GetFiles(mydocs, "*.*", SIO.SearchOption.AllDirectories);
var
query
= from file in files
let lasthour = DateTime.Now.Subtract(new TimeSpan(0, 1, 0, 0))
where SIO.File.GetLastAccessTime(file) >= lasthour &&
(SIO.File.GetAttributes(file) & SIO.FileAttributes.System) == 0
select file;
Notice the presence of the
let
statement, which allows for the definition of values local to the query; let is used to improve readability and efficiency by factoring out common operations.
This example should not be very surprising, since what we are really doing is iterating across an array of filenames ("
files
"), not the file system itself. But this is a design artifact of the .NET Framework, not a limitation of LINQ. A similar example is the searching of a file, which is easily done in LINQ by iterating across the lines of the file:
string filename = ...; // file to search
var
lines
= from line in
SIO.File.ReadAllLines(
filename
)
where line.Contains("class")
select line;
In this example, we are reading all the lines into an array and then searching the array to select those lines containing the character sequence "class."
Need to search the Windows event log? An event log is a collection of
EventLogEntry
objects, and is accessed in .NET by creating an instance of the
EventLog
class. For example, here's how we gain access to the
Application
event log on the current machine:
using SD = System.Diagnostics;
SD.EventLog applog = new SD.EventLog("Application", ".");
Suppose we need to find all events logged by our application for a particular user. This is easily
expressed
as a LINQ query:
string appname = ...; // name of our application, e.g. "SchedulingApp"
string username = ...; // login name for user, e.g. "DOMAIN\hummel"
var
entries
= from entry in
applog.Entries
where entry.Source == appname &&
entry.UserName == username
orderby entry.TimeWritten descending
select entry;
Interestingly, while this query makes perfect sense, it does
not
compile. The issue is that
EnTRies
is a pre-2.0 collection, which means it implements
IEnumerable
and not
IEnumerable<T>
. Since
IEnumerable
is defined in terms of
object
and not a specific type
T
, the C# type inference engine cannot
infer
the type of objects the query expression is working with. The designers of LINQ provide an easy solution in the form of the standard query operator
Cast
. The
Cast
operator wraps a generic enumerable object with a
type-specific
one suitable for LINQ:
var
entries
= from entry in
applog.Entries.Cast<SD.EventLogEntry>()
where entry.Source == appname &&
entry.UserName == username
orderby entry.TimeWritten descending
select entry;
The
Cast
operator allows LINQ to support .NET 1.
x
collections.
As a final example, let's apply LINQ to the world of Visual Studio Tools for Office (VSTO). To search a user's Outlook contacts, the basic query is as follows:
Outlook.MAPIFolder
folder
= this.ActiveExplorer().Session.
GetDefaultFolder( Outlook.OlDefaultFolders.olFolderContacts );
var
contacts
= from contact in
folder.Items.OfType<Outlook.ContactItem>()
where ... // search criteria, e.g. contact.Email1Address != null
select contact;
We take advantage of LINQ's
OfType
query operator, which (a) wraps pre-2.0 collections and (b) filters the collection to return only those objects of the desired type. Here's a query to collect all distinct email addresses from a user's Outlook contacts:
var
emails
= (
from contact in
folder.Items.OfType<Outlook.ContactItem>()
where contact.Email1Address != null
select contact.Email1Address
)
.Distinct();
Finally, given collections of email addresses from different folders or users, we can use LINQ to perform various set operations over these collections:
var
union
= emails.Union(emails2);
var
intersection
= emails.Intersect(emails2);
var
difference
= emails.Except(emails2);
|