It Could Be Done!

the blog about ASP.NET and not only

Using LINQ with System.Reflection Classes

C# 3.0 and LINQ is not only about accessing data in relational databases and LINQ queries can be used against in-memory data structures as well.  Yesterday I had to discover all methods in an assembly satisfying some criteria.  As I as playing with Visual Studio 2008 Beta 2 I tried to use new language features everywhere (well, even where it was unnecessary). 

I used Linq to filter my classes and methods and wrote this code in a functional style as I am not familiar with LINQ query syntax in C# 3.0.

public IEnumerable<Action> LocateActions()
{
    return GetType().Assembly.GetTypes()
     .Where(v => v.GetCustomAttributes(
            typeof(ActionContainerAttribute), true).Length > 0)
     .SelectMany(v => v.GetMethods(BindingFlags.Public |
                                         BindingFlags.Instance))
     .Where(v => v.GetCustomAttributes(
            typeof(ActionAttribute), true).Length > 0)
     .Select(v => new Action(v));
}

My first version works fine for me; I created the second and third versions just to see what is the most readable.  So, the second version of the same method is C# 2.0:

public IEnumerable<Action> LocateActions2()
{
    foreach (Type t in GetType().Assembly.GetTypes())
    {
        if (t.GetCustomAttributes(
                     typeof(ActionContainerAttribute), true).Length > 0)
        {
            foreach (MethodInfo mi in t.GetMethods(BindingFlags.Public |
                                                 BindingFlags.Instance))
            {
                if (mi.GetCustomAttributes(typeof(ActionAttribute),
                                                        true).Length > 0)
                {
                    yield return new Action(mi);
                }
            }
        }
    }
}

and I got the last one from C# 2.0 code.

public IEnumerable<Action> LocateActions3()
{
  return
    from t in GetType().Assembly.GetTypes()
    where
      t.GetCustomAttributes(typeof(ActionContainerAttribute),true).Length> 0
      from mi in t.GetMethods(BindingFlags.Public | BindingFlags.Instance)
  
     where
          mi.GetCustomAttributes(typeof(ActionAttribute), true).Length > 0
        select
          new Action(mi);
    }
}

The first one remains for me the most clear and easy to read, but I believe that not everyone will agree with me. 

Which one is the easiest to read for you?

Posted: Oct 07 2007, 01:54 AM by ysw | with 6 comment(s)
Filed under: , ,

Comments

Bill said:

Did you try to profile them? Which one is fastest, which uses the least memory? Which uses the most?

# October 6, 2007 8:16 PM

Dave said:

I vastly prefer LocateActions2.  LocateActions3 is okay.  I absolutely hate LocateActions.  I prefer the obviousness of the logical structure of LocateActions2.  I suspect LocateActions2 would have the edge in terms of debugability (imagine you want a breakpoint on each mi pass) and maintainability (imagine you need something more complex than a new expression to create the Action objects).  LocateActions2 and LocateActions3 just have a lot less noise than LocateActions.  

# October 7, 2007 4:45 AM

AndrewSeven said:

I find the upside down sql of linq hard to get used to.

The one from the C# 2 looks like it could be parsed by the compiler in the opposite order, and thus written with the select at the top.

The lack of color on the query words make the LocateActions harder to parse, the blue keywords give the other examples clear separation of the query from normal method calls.

The first two example suffer from having line breaks on the page, so they are less clear than they could be.

For such a simple set of loops, I would use the foreach, it is efficient and easy for other people to understand ;)

With the lines organized differently , this still suffers from "what is v?"

public IEnumerable<Action> LocateActions()

{

   return GetType().Assembly.GetTypes()

       .Where

           (v => v.GetCustomAttributes( typeof(ActionContainerAttribute), true).Length > 0)

       .SelectMany

           (v => v.GetMethods(BindingFlags.Public | BindingFlags.Instance))

        .Where

           (v => v.GetCustomAttributes(typeof(ActionAttribute), true).Length > 0)

        .Select

        (v => new Action(v));

}

# October 7, 2007 11:52 AM

Glen said:

I like the last one

# October 7, 2007 10:39 PM

Anderson Imes said:

I'm digging the last one as well.  I think that the amount of syntax characters necessary to use the linq to collections extension methods makes it less readable.

I would imagine they would all perform similarly, since 99.9% of the execution time here will be in any reflection Get* methods, which they all use.  Those things are slow (not many other options, however).

Let us know if you do profile each option.

# October 8, 2007 3:46 AM

ysw said:

I haven't run any performance tests for this sample.  I think that readability will not be very important if this code has to run in some inner loop.  Anyway, I am sure that LocateActions2 will be the fastest.  It not the case like described in diditwith.net/.../PerformanceOfForeachVsListForEach.aspx. No one of LINQ methods are implemented natively by arrays and I believe compiler optimizes loops over arrays even in iterator (yield return) methods.

However, I compared what compiler generates for LocateActions2 with LocateActions.  I must say that it would be a little slower as my first original version.  C# compiler introduces one additional anonymous class which is produced by .SelectMany.  

This is copy/paste from Reflector:

.SelectMany(delegate (Type t) {

           return t.GetMethods(BindingFlags.Public | BindingFlags.Instance);

       }, delegate (Type t, MethodInfo mi) {

           return new { t = t, mi = mi };

# October 8, 2007 5:44 PM