patterncsharpMinor
Simplify a complex LINQ query
Viewed 0 times
linqsimplifyquerycomplex
Problem
Any suggestions how I could simplify this LINQ query?
If you need more information, just ask.
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):
The first thing I notice is that the overall structure of the query is:
I'd split the complicated looking filter out into a method to start with:
That gives us two simpler queries to look at. The only thing I can see in the first part is that the repeated
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:
We're now using the
You may notice that the two
The first line is redundant. So we can remove it:
The only thing we ever do to
We're now asking the computer to iterate over all the events and select its name for every
```
private static bool IsAsyncCompletableType(Type type)
{
var eventNamesFromMethods = from method in type.GetMethods()
where method.Name
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
operationNameand 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.