Combine two LINQ queries?
I think I have a mental block, but can someone please tell me how to combine these two LINQ operators into one?
/// <summary>
/// Returns an array of Types that implement the supplied generic interface in the
/// current AppDomain.
/// </summary>
/// <param name="interfaceType">Type of generic interface implemented</param>
/// <param name="includeAbstractTypes">Include Abstract class types in the search</param>
/// <param name="includeInterfaceTypes">Include Interface class types in the search</param>
/// <returns>Array of Types that implement the supplied generic interface</returns>
/// <remarks>
/// History.<br/>
/// 10/12/2008 davide Method creation.<br/>
/// </remarks>
public static Type[] GetTypesImplementingGenericInterface(Type interfaceType, bool includeAbstractTypes, bool includeInterfaceTypes)
{
// Use linq to find types that implement the supplied interface.
var allTypes = AppDomain.CurrentDomain.GetAssemblies().ToList()
.SelectMany(s => s.GetTypes())
.Where(p => p.IsAbstract == includeAbstractTypes
&& p.IsInterface == includeInterfaceTypes);
var implementingTypes = from type in allTypes
from intf in type.GetInterfaces().ToList()
where intf.FullName != null && intf.FullName.Contains(interfaceType.FullName)
select type;
return implementingTypes.ToArray<Type>();
}
I avoid IsAssignableFrom as it seems to fail when it doesn't supply a specific type of generic interface, so I believe that using FullName caparison over IsAssignableFrom should suffice, e.g .:
namespace Davide
{
interface IOutput<TOutputType> { }
class StringOutput : IOutput<string> { }
}
typeof (IOutput <>). FullName will return "Davide + IOutput`1"
typeof (StringOutput) .GetInterfaces () [0] .FullName will return "Davide + IOutput`1 [[System.String, mscorlib, Version = 2.0.0.0, Culture = neutral, PublicKeyToken = b77a5c561934e089]]"
Therefore using FullName.Contains should suffice
source to share
After a short discussion with Jon Skeet and a little more thoughtful, I posted the following answer. I changed the method using GetGenericTypeDefinition rather than FullName.Contains, this would be a more robust solution. I also modified the LINQ Where clauses for IsAbstract and IsInterface as they did not exclude types as expected. Thanks everyone for your feedback.
/// <summary>
/// Returns an array of Types that implement the supplied generic interface in the
/// current AppDomain.
/// </summary>
/// <param name="interfaceType">Type of generic interface implemented</param>
/// <param name="excludeAbstractTypes">Exclude Abstract class types in the search</param>
/// <param name="excludeInterfaceTypes">Exclude Interface class types in the search</param>
/// <returns>Array of Types that implement the supplied generic interface</returns>
/// <remarks>
/// History.<br/>
/// 11/12/2008 davide Created method.<br/>
/// 11/12/2008 davide Altered method to use a two LINQ query pass.<br/>
/// 11/12/2008 davide Altered method to use optimised combined LINQ query.<br/>
/// 12/12/2008 davide Altered method and replaced FullName criteria match with GetGenericTypeDefinition.<br/>
/// </remarks>
public static Type[] GetTypesImplementingGenericInterface(Type interfaceType, bool excludeAbstractTypes, bool excludeInterfaceTypes)
{
if (!interfaceType.IsGenericType)
{
throw new ArgumentException("Supplied interface is not a Generic type");
}
if (interfaceType.ContainsGenericParameters)
{
interfaceType = interfaceType.GetGenericTypeDefinition();
}
// Use linq to find types that implement the supplied generic interface.
var implementingTypes = from assembly in AppDomain.CurrentDomain.GetAssemblies()
from type in assembly.GetTypes()
where (type.IsAbstract != excludeAbstractTypes) || (!excludeAbstractTypes)
where (type.IsInterface != excludeInterfaceTypes) || (!excludeInterfaceTypes)
from intf in type.GetInterfaces()
where intf.IsGenericType && intf.GetGenericTypeDefinition() == interfaceType
select type;
return implementingTypes.ToArray<Type>();
}
source to share
SelectMany translates to the second "from":
var implementors = from assembly in AppDomain.CurrentDomain.GetAssemblies()
from type in assembly.GetTypes()
where type.IsAbstract == includeAbstractTypes
where type.IsInterface == includeInterfaceTypes
from intf in type.GetInterfaces()
where intf.FullName != null &&
intf.FullName.Contains(interfaceType.FullName)
select type;
I've split the clauses into several "where" clauses for subjective clarity, by the way.
This compiles, but I haven't tested if it actually works or not. As the other answer showed, you can use "Any" with GetInterfaces () instead of the final "from" clause.
Note that there is no need to constantly rewrite ToList () all over the place - LINQ is designed to work on sequences.
By the way, I'm not sure why you are passing through type.GetInterfaces () to check. Is there something different (and desirable) from this using Type.IsAssignableFrom ? This will make it easier:
var implementors = from assembly in AppDomain.CurrentDomain.GetAssemblies()
from type in assembly.GetTypes()
where type.IsAbstract == includeAbstractTypes
where type.IsInterface == includeInterfaceTypes
where interfaceType.IsAssignableFrom(type)
select type;
Do you actually have the same interface type name in another assembly?
source to share
this will do:
public static Type[] GetTypesImplementingGenericInterface(Type interfaceType, bool includeAbstractTypes, bool includeInterfaceTypes)
{
// Use linq to find types that implement the supplied interface.
var allTypes = AppDomain.CurrentDomain.GetAssemblies().ToList()
.SelectMany(s => s.GetTypes())
.Where(p => p.IsAbstract == includeAbstractTypes
&& p.IsInterface == includeInterfaceTypes
&& p.GetInterfaces().Any(i=>i.FullName != null && i.FullName.Contains(interfaceType.FullName))
);
//var implementingTypes = from type in allTypes
// from intf in type.GetInterfaces().ToList()
// where intf.FullName != null && intf.FullName.Contains(interfaceType.FullName)
// select type;
//return implementingTypes.ToArray<Type>();
return allTypes.ToArray();
}
source to share
Can you suggest another solution?
public static Type[] GetTypesImplementingGenericInterface(Type interfaceType, bool includeAbstractTypes, bool includeInterfaceTypes)
{
// Use linq to find types that implement the supplied interface.
var implementingTypes = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(s => s.GetTypes())
.Where(p => interfaceType.IsAssignableFrom(p)
&& (
(p.IsAbstract && includeAbstractTypes)
|| (p.IsInterface && includeInterfaceTypes)
|| (!p.IsAbstract && !p.IsInterface)
)
);
return implementingTypes.ToArray<Type>();
}
source to share