Stupid LINQ Tricks

Tuesday, September 2, 2008

Over a month ago I did a presentation on LINQ and promised a few people I’d share the code from the session. Better late than never, eh?

We warmed up by building our own filtering operator to use in a query. The operator takes an Expression<Predicate<T>>, which we need to compile before we invoking the predicate inside.

public static class MyExtensions
{
    public static IEnumerable<T> Where<T>(
                  this IEnumerable<T> sequence,
                  Expression<Predicate<T>> filter)
    {
        foreach (T item in sequence)
        {
            if (filter.Compile()(item))
            {
                yield return item;
            }
        }
    }
}

The following query uses our custom Where operator:

IEnumerable<Employee> employees = new List<Employee>()
{
    new Employee() { ID= 1, Name="Scott" },
    new Employee() { ID =2, Name="Paul" }
};


Employee scott =
    employees.Where(e => e.Name == "Scott").First();

Of course, if we are just going to compile and invoke the expression there is little advantage to using an Expression<T>, but it generally turns into an “a-ha!” moment when you show someone the difference between an Expression<Predicate<T>> and a plain Predicate<T>. Try it yourself in a debugger.

We also wrote a LINQ version of “Hello, World!” that reads text files from a temp directory (a.txt would contain “Hello,”, while b.txt would contain “World!”. A good demonstration of map-filter-reduce with C# 3.0.

var message = Directory.GetFiles(@"c:\temp\")
                       .Where(fname => fname.EndsWith(".txt"))
                       .Select(fname => File.ReadAllText(fname))
                       .Aggregate(
                           new StringBuilder(),
                          (sb, s) => sb.Append(s).Append(" "),
                          sb => sb.ToString()
                       );


Console.WriteLine(message);

Moving into NDepend territory, we also wrote a query to find the namespaces with the most types (for referenced assemblies only):

var groups = Assembly.GetExecutingAssembly()
         .GetReferencedAssemblies()
         .Select(aname => Assembly.Load(aname))
         .SelectMany(asm => asm.GetExportedTypes())
         .GroupBy(t => t.Namespace)
         .OrderByDescending(g => g.Count())
         .Take(10);

foreach (var group in groups)
{
    Console.WriteLine("{0} {1}", group.Key, group.Count());
    foreach (var type in group)
    {
        Console.WriteLine("\t" + type.Name);
    }
}

And finally, some LINQ to XML code that creates an XML document out of all the executing processes on the machine:

XNamespace ns = "http://odetocode.com/schemas/linqdemo";
XNamespace ext = "http://odetocode.com/schemas/extensions";

XDocument doc =
    new XDocument(
        new XElement(ns + "Processes",
            new XAttribute(XNamespace.Xmlns + "ext", ext),
            from p in Process.GetProcesses()
            select new XElement(ns + "Process",
               new XAttribute("Name", p.ProcessName),
               new XAttribute(ext + "PID", p.Id))));

Followed by a query for the processes ID of any mspaint instances:

var query =
   (from e in doc.Descendants(ns + "Process")
    where (string)e.Attribute("Name") == "mspaint"
    select (string)e.Attribute(ext + "PID"));

More on LINQ to come…


Comments
Amozabi Kouysk Tuesday, September 2, 2008
I noted that Predicate generate a delegate and source code point to it; Expression injects the Expression Tree that represent Lambda Expression.

Is there other consideration?
scott Tuesday, September 2, 2008
Amozabi: That's right. Treating code as data (an expression tree) is a powerful concept and enables all the remote LINQ providers to transform C# to SQL, Web Service calls, etc.
James Curran Tuesday, September 2, 2008
Shouldn't the lines in Where():
foreach (T item in sequence)
{
if (filter.Compile()(item))

Actually be:
Predicte pred = filter.Compile();
foreach (T item in sequence)
{
if (pred(item))

There's no really sense in recompiling it every time...
scott Tuesday, September 2, 2008
James: Yes. Good point!
John S. Wednesday, September 3, 2008
Picking nits here, but you can do e.Attribute("Name").Value == "mspaint" instead of casting.
scott Wednesday, September 3, 2008
@John:

Good point - I did show both techniques.

The one advantage to casting is when dealing with optional attributes. Casting will yield a null string reference while .Value will throw an exception.
Ross Neilson Friday, September 5, 2008
This served as a good reminder to me that LINQ isn't just for doing databasey tasks. Providing you give it an IEnumerable it will work on anything.

I was doing some work with reflection today and started off by looping through a load of PropertyInfos. Then I remembered this blog post and trimmed it all down to one line.

Great post Scott, thanks very much.
Comments are now closed.
by K. Scott Allen K.Scott Allen
My Pluralsight Courses
The Podcast!