HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Simplify a complex LINQ query

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
linqsimplifyquerycomplex

Problem

Any suggestions how I could simplify this LINQ query?

from type in assembly.GetTypes()
where type.IsPublic && !type.IsSealed && type.IsClass
where (from method in type.GetMethods()
       from typeEvent in type.GetEvents()
       where method.Name.EndsWith("Async")
       where typeEvent.Name.EndsWith("Completed")
       let operationName = method.Name.Substring(0, method.Name.Length - "Async".Length)
       where typeEvent.Name == operationName + "Completed"
       select new { method, typeEvent }).Count() > 0
select type;


assembly is of type System.Reflection.Assembly.
If you need more information, just ask.

Solution

Original code (wrapped up in a method):

public IEnumerable GetAsyncCompletableTypes(Assembly assembly)
{
    return from type in assembly.GetTypes()
           where type.IsPublic && !type.IsSealed && type.IsClass
           where (from method in type.GetMethods()
                  from typeEvent in type.GetEvents()
                  where method.Name.EndsWith("Async")
                  where typeEvent.Name.EndsWith("Completed")
                  let operationName = method.Name.Substring(0, method.Name.Length - "Async".Length)
                  where typeEvent.Name == operationName + "Completed"
                  select new { method, typeEvent }).Count() > 0
           select type;
}


The first thing I notice is that the overall structure of the query is:

  • Find me all types in the assembly



  • Where the type is a public, non-sealed class



  • And where the type passes a complicated looking filter.



I'd split the complicated looking filter out into a method to start with:

public IEnumerable GetAsyncCompletableTypes(Assembly assembly)
{
    return from type in assembly.GetTypes()
       where type.IsPublic && !type.IsSealed && type.IsClass
       where IsAsyncCompletableType(type)
       select type;
}

private static bool IsAsyncCompletableType(Type type)
{
    return (from method in type.GetMethods()
        from typeEvent in type.GetEvents()
        where method.Name.EndsWith("Async")
        where typeEvent.Name.EndsWith("Completed")
        let operationName = method.Name.Substring(0, method.Name.Length - "Async".Length)
        where typeEvent.Name == operationName + "Completed"
        select new { method, typeEvent }).Count() > 0;
}


That gives us two simpler queries to look at. The only thing I can see in the first part is that the repeated where can be collapsed into a single one:

public IEnumerable GetAsyncCompletableTypes(Assembly assembly)
{
    return from type in assembly.GetTypes()
       where type.IsPublic && !type.IsSealed && type.IsClass && IsAsyncCompletableType(type)
       select type;
}


Onto the second part. The lines in the query seem to alternate between being related to the methods and the events - reordering the lines will make it clearer what's going on:

private static bool IsAsyncCompletableType(Type type)
{
    return (from method in type.GetMethods()
        where method.Name.EndsWith("Async")
        let operationName = method.Name.Substring(0, method.Name.Length - "Async".Length)
        from typeEvent in type.GetEvents()
        where typeEvent.Name.EndsWith("Completed")
        where typeEvent.Name == operationName + "Completed"
        select 0).Any();
}


We're now using the method variable up to the let operation, and never using it again, so we can select the operationName in a subquery instead of using let.

private static bool IsAsyncCompletableType(Type type)
{
    var operationNames = from method in type.GetMethods()
        where method.Name.EndsWith("Async")
        select method.Name.Substring(0, method.Name.Length - "Async".Length);

    return (from operationName in operationNames            
        from typeEvent in type.GetEvents()
        where typeEvent.Name.EndsWith("Completed")
        where typeEvent.Name == operationName + "Completed"
        select 0).Any();
}


You may notice that the two where lines don't make a lot of sense together at this point:

  • Pick events



  • Where the name ends with "Completed"



  • And where the name starts with operationName and ends with "Completed"



The first line is redundant. So we can remove it:

private static bool IsAsyncCompletableType(Type type)
{
    var operationNames = from method in type.GetMethods()
        where method.Name.EndsWith("Async")
        select method.Name.Substring(0, method.Name.Length - "Async".Length);

    return (from operationName in operationNames
        from typeEvent in type.GetEvents()
        where typeEvent.Name == operationName + "Completed"
        select 0).Any();
}


The only thing we ever do to operationName is add "Completed" to it - we might as well do that when we create the operationName (and rename it appropriately):

private static bool IsAsyncCompletableType(Type type)
{
    var eventNamesFromMethods = from method in type.GetMethods()
        where method.Name.EndsWith("Async")
        select method.Name.Substring(0, method.Name.Length - "Async".Length) + "Completed";

    return (from eventNameFromMethod in eventNamesFromMethods
        from typeEvent in type.GetEvents()
        where typeEvent.Name == eventNameFromMethod
        select 0).Any();
}


We're now asking the computer to iterate over all the events and select its name for every eventNameFromMethod. We can pre-compute these and put them into a fast lookup container - a HashSet:

```
private static bool IsAsyncCompletableType(Type type)
{
var eventNamesFromMethods = from method in type.GetMethods()
where method.Name

Code Snippets

public IEnumerable<Type> GetAsyncCompletableTypes(Assembly assembly)
{
    return from type in assembly.GetTypes()
           where type.IsPublic && !type.IsSealed && type.IsClass
           where (from method in type.GetMethods()
                  from typeEvent in type.GetEvents()
                  where method.Name.EndsWith("Async")
                  where typeEvent.Name.EndsWith("Completed")
                  let operationName = method.Name.Substring(0, method.Name.Length - "Async".Length)
                  where typeEvent.Name == operationName + "Completed"
                  select new { method, typeEvent }).Count() > 0
           select type;
}
public IEnumerable<Type> GetAsyncCompletableTypes(Assembly assembly)
{
    return from type in assembly.GetTypes()
       where type.IsPublic && !type.IsSealed && type.IsClass
       where IsAsyncCompletableType(type)
       select type;
}

private static bool IsAsyncCompletableType(Type type)
{
    return (from method in type.GetMethods()
        from typeEvent in type.GetEvents()
        where method.Name.EndsWith("Async")
        where typeEvent.Name.EndsWith("Completed")
        let operationName = method.Name.Substring(0, method.Name.Length - "Async".Length)
        where typeEvent.Name == operationName + "Completed"
        select new { method, typeEvent }).Count() > 0;
}
public IEnumerable<Type> GetAsyncCompletableTypes(Assembly assembly)
{
    return from type in assembly.GetTypes()
       where type.IsPublic && !type.IsSealed && type.IsClass && IsAsyncCompletableType(type)
       select type;
}
private static bool IsAsyncCompletableType(Type type)
{
    return (from method in type.GetMethods()
        where method.Name.EndsWith("Async")
        let operationName = method.Name.Substring(0, method.Name.Length - "Async".Length)
        from typeEvent in type.GetEvents()
        where typeEvent.Name.EndsWith("Completed")
        where typeEvent.Name == operationName + "Completed"
        select 0).Any();
}
private static bool IsAsyncCompletableType(Type type)
{
    var operationNames = from method in type.GetMethods()
        where method.Name.EndsWith("Async")
        select method.Name.Substring(0, method.Name.Length - "Async".Length);

    return (from operationName in operationNames            
        from typeEvent in type.GetEvents()
        where typeEvent.Name.EndsWith("Completed")
        where typeEvent.Name == operationName + "Completed"
        select 0).Any();
}

Context

StackExchange Code Review Q#13545, answer score: 7

Revisions (0)

No revisions yet.