5

I'm aware of this question, and it's follow-up, and also this one, but I can't put them together in a way which will help me do what I want to do:

I have a generic type, and I want to check that T is a struct OR if it implements IEnumerable<T2>, then I'd like to check that T2 is a struct.

So far, I've got to here ('scuse the scrappy code, this is experimental):

private class TestClass<T>
{
    public TestClass()
    {
        Type type = typeof(T);
        //(detecting T == IEnumerable<something> ommitted for clarity)
        Type enumerableType = type.GetInterfaces()
                .Where(t => t.IsGenericType)
                .Select(t => t.GetGenericTypeDefinition())
                .Where(t => t == typeof(IEnumerable<>))
                .FirstOrDefault();
        if(enumerableType != null) 
        {
            Type enumeratedType = type.GetGenericArguments().First();
            if(!enumeratedType.IsValueType) //throw etc...
        }
    }
}

The problem I have is that enumerableType is IEnumerable<>, so the enumeratedType comes out as T, not whatever I've passed in (eg. new TestClass<int[]>()).

Community
  • 1
  • 1
Benjol
  • 63,995
  • 54
  • 186
  • 268

3 Answers3

4

Your problem is that you've selected away the type that has all the data in favor of it's erased generic type template.

Try:

    Type enumerableType = type.GetInterfaces()
            .Where(t => t.IsGenericType)
            .Where(t => t.GetGenericTypeDefinition() == typeof(IEnumerable<>))
            .Select(t => t.GetGenericArguments()[0])
            .FirstOrDefault();
Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • Thanks, it works, and I'll trust you for the edge cases (slightly out of my depth in the comments on dtb's answer). – Benjol Jan 07 '11 at 14:50
  • @Benjol: Depending on what version of .NET you're using, you may also want an explicit check for arrays, since [in .NET 2.0 `GetInterfaces` was broken on array types](https://connect.microsoft.com/VisualStudio/feedback/details/93995/getinterfaces-bug-for-typed-array). dtb's got that particular test right... `if (type.IsArray) return type.GetElementType();`. – Ben Voigt Jan 07 '11 at 15:38
2

From Matt Warren's Blog:

internal static class TypeSystem {
    internal static Type GetElementType(Type seqType) {
        Type ienum = FindIEnumerable(seqType);
        if (ienum == null) return seqType;
        return ienum.GetGenericArguments()[0];
    }
    private static Type FindIEnumerable(Type seqType) {
        if (seqType == null || seqType == typeof(string))
            return null;
        if (seqType.IsArray)
            return typeof(IEnumerable<>).MakeGenericType(seqType.GetElementType());
        if (seqType.IsGenericType) {
            foreach (Type arg in seqType.GetGenericArguments()) {
                Type ienum = typeof(IEnumerable<>).MakeGenericType(arg);
                if (ienum.IsAssignableFrom(seqType)) {
                    return ienum;
                }
            }
        }
        Type[] ifaces = seqType.GetInterfaces();
        if (ifaces != null && ifaces.Length > 0) {
            foreach (Type iface in ifaces) {
                Type ienum = FindIEnumerable(iface);
                if (ienum != null) return ienum;
            }
        }
        if (seqType.BaseType != null && seqType.BaseType != typeof(object)) {
            return FindIEnumerable(seqType.BaseType);
        }
        return null;
    }
}
dtb
  • 213,145
  • 36
  • 401
  • 431
  • +1, All the other answers make the same mistake that you do NOT have, ie `t == typeof(IEnumerable<>)`. That wont ever work, as an instance of that type can NEVER exist. – leppie Jan 07 '11 at 14:13
  • @leppie, uh, talking about instances of types gets a bit hairy for me when it comes to reflexion. Are you saying that you can never instantiate an object of type `IEnumerable<>`, or that you can never have `typeof(T) == typeof(IEnumerable<>)` - as in `typeof(IEnumerable`)? – Benjol Jan 07 '11 at 14:21
  • @leppie: Look closer, GetGenericTypeDefinition() is called before the comparison, it will match. – Ben Voigt Jan 07 '11 at 14:23
  • @dtb: What's this give for `Dictionary`? – Ben Voigt Jan 07 '11 at 14:30
  • @BenVoigt, gives `System.Collections.Generic.KeyValuePair`2[System.Object,System.String]` for me – Benjol Jan 07 '11 at 14:37
  • @dtb, @Benjol: That wasn't a particularly good example, because `KeyValuePair` is a struct and thus doesn't participate in interface variance. However, the code is broken, it doesn't work for `MyDictionary` where `interface MyDictionary : IEnumerable { }` – Ben Voigt Jan 07 '11 at 14:44
  • I wonder if the .NET team even realized that this introduced a breaking change between .NET 3.5 and .NET 4... I'd guess that the answer is "yes, but they couldn't think of a case where it mattered". Well here's one. – Ben Voigt Jan 07 '11 at 14:45
  • @Ben Voigt: My comment was made before your answer. Yours will work. :) Actually I missed the GetInterfaces part. – leppie Jan 07 '11 at 14:55
  • @Benjol: It is an open type. In fact, a derived type cannot even exist. It has to be closed, iow provided with all the type parameters. – leppie Jan 07 '11 at 14:56
  • @leppie: My comment was also made before my answer... I was talking about the code in the question. – Ben Voigt Jan 07 '11 at 15:37
0

I believe this code does what you want it to.
You might want to clean it up a bit though.

public class TestClass<T>
{
    public TestClass()
    {
        bool hasStruct = false;
        Type t1 = this.GetType().GetGenericArguments()[0];
        if(t1.IsValueType){
            hasStruct = true;
        }
        if (t1.IsGenericType)
        {
            Type t = t1.GetGenericArguments()[0];
            if (t.IsValueType)
            {
                hasStruct = true;
            }
        }

    }
}
Kristof
  • 3,267
  • 1
  • 20
  • 30