views:

929

answers:

6

I want to write a method that uses Reflection to tell whether a given Type implements IList<T>. For example:

IsGenericList(typeof(int))                       // should return false
IsGenericList(typeof(ArrayList))                 // should return false
IsGenericList(typeof(IList<int>))                // should return true
IsGenericList(typeof(List<int>))                 // should return true
IsGenericList(typeof(ObservableCollection<int>)) // should return true

In my usage, I can assume that the type will always be an instantiated generic type (or something that's not generic at all).

Unfortunately, this isn't as easy as it ought to be. The obvious solution:

public bool IsGenericList(Type type)
{
    return typeof(IList<>).IsAssignableFrom(type);
}

doesn't work; it always returns false. Apparently non-instantiated generic types like IList<> don't implement IsAssignableFrom the way I'd expect them to: IList<> is not assignable from List<T>.

I've also tried this:

public bool IsGenericList(Type type)
{
    if (!type.IsGenericType)
        return false;
    var genericTypeDefinition = type.GetGenericTypeDefinition();
    return typeof(List<>).IsAssignableFrom(genericTypeDefinition);
}

I.e., turn type into its non-instantiated generic, like IList<int> -> IList<>, and then try IsAssignableFrom again. That will return true when type is an instantiated IList<T> such as IList<int>, IList<object>, etc. But it returns false for classes that implement IList<T> such as List<int>, ObservableCollection<double>, etc., so apparently IList<> is not assignable from List<>. Again, not what I would expect.

How do I go about writing IsGenericList and making it work as in the above examples?

A: 

Use the "is" operator:

The is operator is used to check whether the run-time type of an object is compatible with a given type.

jeffamaphone
I don't have an instance. I have the Type.
Joe White
This won't work with IList<>, only with IList<int>, etc. You need to have a concrete type for "is" to work...
Reed Copsey
+8  A: 

In fact, you cannot have an instance of a generic type definition. Therefore, the IsAssignableFrom() method works as expected. To achieve what you want, do the following:

public bool IsGenericList(Type type)
    if (type == null) {
     throw new ArgumentNullException("type");
    }
    foreach (Type @interface in type.GetInterfaces()) {
     if (@interface.IsGenericType) {
      if (@interface.GetGenericTypeDefinition() == typeof(ICollection<>)) {
       // if needed, you can also return the type used as generic argument
       return true;
      }
     }
    }
    return false;
}

Just out of curiosity, what do you need this for?

Lucero
+1 - This works, and is the only solution here that currently does - I just realized you had this after I posted the same thing.
Reed Copsey
Never saw @ to use keywords as variable names in any production code before.
VVS
Good solution. :) Small point however: using the @ symbols is generally not recommended practice. (You could just call the variable `i` or `curInterface` instead to resolve the name clash.)
Noldorin
ReSharper gave that identifier this name when I auto-expanded the foreach on type.GetInterfaces() - I usually don't use that feature either when choosing names myself. But I wanted to be quick so I didn't refactor it. ;)
Lucero
You're missing the { for the method and you used ICollection<> instead of IList<>, but otherwise this works perfectly, thanks!
Joe White
As for why we need this: we're translating code from Delphi/Win32 to C#/.NET, and our workarounds for Delphi fixed-size arrays don't play nice with our workarounds for test-framework resource cleanup, so we're deciding whether to add yet another workaround for the combination, and I want to know how often that workaround would need to apply. Specifically, I need a one-off tool to tell me how many test-case classes have fields whose types implement IList or IList<T>. Aren't you glad you asked? (grin)
Joe White
A: 

Lucero/Reed Copsey both have the right solution now. Just to make it more concise, here it is in LINQified form:

var isGenericList = type.GetInterfaces().Any(t => t.IsGenericType && 
    t.GetGenericTypeDefinition() == typeof(IList<>));
Noldorin
Won't work, since GetInterfaces() will not return the generic type definitions, only the generic types (such as IList<int>).
Lucero
Tested - does not work (it was my first attempt, too).
Reed Copsey
Yeah, so it doesn't. I've updated it and it seems to work now however.
Noldorin
You can rewrite my solution using Linq if you want... ;)
Lucero
I didn't choose Linq because from the question it is only clear that generic are available, not what version of the C# compiler is being used.
Lucero
It fails again for case #3 ... have a look at my solution - shorter and faster
tanascius
Lucero's solution seems more robust, as it doesn't involve strings.
Noldorin
No problem - but your solution still does not work ...
tanascius
A: 

This passes your tests ...

public static bool IsGenericList( Type type )
{
  return type.Name == "IList`1" || type.GetInterface( "IList`1" ) != null;
}
tanascius
type doesn't have to be generic to implement a generic interface. You can have this: class IntList: object, IList<int> { .... } which doesn't make IntList generic.
Lucero
The IsGeneric check is not neccessary at all
tanascius
A: 

Have you tried calling Type.GetInterface()? It's not completely clear from the help, but I think that it will search interfaces implemented by base types, in addition to the type itself. If not, you can always loop through Type.BaseType and call GetInterface() again.

Andy
GetInterface only works if you know the specific generic argument, but won't work for IList<T>.
Reed Copsey
+1  A: 

Using this: http://msdn.microsoft.com/en-us/library/system.type.findinterfaces.aspx

I tried this:

 public class Test : IList<string>
 {
//implementation left out...
 }

class Program
    {
        static void Main(string[] args)
        {
            Test t = new Test();
            TypeFilter myFilter = new TypeFilter(MyInterfaceFilter);

            Type type = t.GetType();
            Type[] x = type.FindInterfaces(myFilter, "System.Collections.Generic.IList");
            Console.WriteLine(x.Length);

        }

        public static bool MyInterfaceFilter(Type typeObj, Object criteriaObj)
        {
            if (typeObj.ToString().Contains(criteriaObj.ToString()))
                return true;
            else
                return false;
        }
    }
Irwin
Using a non-strong-name to identify a type is very unsafe. Anyone can create a type with that name, it doesn't mean that it's the type you want to test for.
Lucero
True, you could just replace it with this:Type[] x = type.FindInterfaces(myFilter, typeof(IList<>).FullName);
Irwin