Flylib.com

Books Software

 
 
 

Section 5.5. LINQ to IEnumerable


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);