0

Given a passed-in IEnumerable, is there any built-in method to return a best-matching base type for all the items in the IEnumerable, that handles nullable/non-nullable types and inheritance?

Something like this:

var hs = new HashSet<object> {1,2,3,null};
Type t = GetSharedType(hs); //returns typeof(int?)

hs = new HashSet<object> {new BaseClass(), new DerivedClass()};
t = GetSharedType(hs); //returns typeof(BaseClass)

hs = new HashSet<object> {"1", 1};
t = GetSharedType(hs); //returns typeof(object) or null

(I am aware that I can write my own; my question is if there is something built-in).

Zev Spitz
  • 13,950
  • 6
  • 64
  • 136
  • Maybe you should instead refactor your code. Why do you store objects in the same collection which type you don't know? – Tim Schmelter May 23 '16 at 07:41
  • @TimSchmelter It's not my collection. (Specifically, it's the ItemsSource for a DataGrid for which I want to build a `Predicate` at runtime, in a generic fashion.) – Zev Spitz May 23 '16 at 07:45
  • 1
    Obviously, there are a few places in the *compilers* where they have to make a decision about a "best type" given several candidates, but they're usually working with a severely restricted set (e.g. just two or three types) and usually work with the restriction that the return value must be one of the types passed in (and not some e.g. common supertype). I don't think you're working with such restrictions in place. – Damien_The_Unbeliever May 23 '16 at 07:52
  • @VadimMartynov If you would like to post an answer, I will accept it. – Zev Spitz May 23 '16 at 21:39
  • @ZevSpitz I published my answer. It will not work with your first scenario (with `null`) but works on most scenarios. – Vadim Martynov May 26 '16 at 08:03
  • @VadimMartynov That's excellent, because I've already written code that handles the `null` case. – Zev Spitz May 26 '16 at 09:10

2 Answers2

2

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();
Community
  • 1
  • 1
Vadim Martynov
  • 8,602
  • 5
  • 31
  • 43
-1

No, there is no built in type for doing this. As you have identified by yourself that you have to write by your own. Something like

public static Type GetEnumerableType<T>(this IEnumerable<T> enumerable)
{
    return typeof(T);
}
Rahul Tripathi
  • 168,305
  • 31
  • 280
  • 331