No, there is no built-in mechanism to do this. You can combine several reflection APIs.
First you can retrieve real types for each object in collection with GetType():
IEnumerable<Type> realTypes = hs.Select(o => o.GetType());
Now you will have collection of Type class that have BaseType property and GetInterfaces() method. Ypu can use this code to get all hierarchy for each type:
public static IEnumerable<Type> GetParentTypes(this Type type)
{
// is there any base type?
if ((type == null) || (type.BaseType == null))
{
yield break;
}
// return all implemented or inherited interfaces
foreach (var i in type.GetInterfaces())
{
yield return i;
}
// return all inherited types
var currentBaseType = type.BaseType;
while (currentBaseType != null)
{
yield return currentBaseType;
currentBaseType= currentBaseType.BaseType;
}
}
You can use it to get collection of hierarchies:
IEnumerable<IEnumerable<Type>> baseTypes = realTypes.Select(t => t.GetParentTypes());
Next step is to merge all this list to have only intersected values. You can do it with Enumerable.Intersect method and this code:
public static IEnumerable<T> IntersectAllIfEmpty<T>(params IEnumerable<T>[] lists)
{
IEnumerable<T> results = null;
lists = lists.Where(l => l.Any()).ToArray();
if (lists.Length > 0)
{
results = lists[0];
for (int i = 1; i < lists.Length; i++)
results = results.Intersect(lists[i]);
}
else
{
results = new T[0];
}
return results;
}
Finally, we have:
IEnumerable<Type> realTypes = hs.Select(o => o.GetType());
IEnumerable<IEnumerable<Type>> baseTypes = realTypes.Select(t => t.GetParentTypes());
IEnumerable<Type> inersectedBaseTypes = IntersectAllIfEmpty(baseTypes);
Then we can use Type.IsAssignableFrom() method to iterate each type and ensure that one of them is assignable only from themself:
Type mostConcreteType = inersectedBaseTypes.Where(t => inersectedBaseTypes.Count(bt => t.IsAssignableFrom(bt)) == 1).FirstOrDefault();