bool IsTypeAGenericList(Type listType)
{
typeof(IList<>).IsAssignableFrom(listType.GetGenericTypeDefinition())
}
возвращает false, если задано typeof(List<int>)
.
Я предполагаю, что это связано с тем, что параметры двух типов могут быть разными, правильно?
Собственно, это работает:
public static bool IsGenericList(Type type)
{
if (!type.IsGenericType)
return false;
var genericArguments = type.GetGenericArguments();
if (genericArguments.Length != 1)
return false;
var listType = typeof (IList<>).MakeGenericType(genericArguments);
return listType.IsAssignableFrom(type);
}
Это действительно связано с открытыми построенными типами.
Когда вы говорите:
class List<T> : IList<T>
Вы на самом деле говорите: мой класс называется List, он имеет один параметр типа T, и он реализует интерфейс, который построен из IList < > используя тот же T. Таким образом, T в части определения и T в части "реализует" оба относятся к одному и тому же типу параметра - вы объявляете его перед двоеточием, после чего вы сразу ссылаетесь на него после двоеточия.
Он запутывается, потому что параметр типа IList<>
также называется T - но это совсем другой параметр типа. Поэтому давайте повторно объявим наш конкретный класс следующим образом:
class List<U> : IList<U>
Это полностью эквивалентно вышесказанному, только теперь мы можем сказать "U", когда мы ссылаемся на параметр типа List и T, когда мы ссылаемся на один из IList. Они разные.
Теперь становится легче понять, почему определение универсального типа List<U>
(что вы имеете в виду, когда говорите typeof(List<>)
), не реализует определение типа genericc IList<T>
(что вы имеете в виду, когда говорите typeof(IList<>)
), а скорее реализует открытый родовой построенный тип IList<U>
(то есть IList, построенный с помощью париметра типа List).
Таким образом, общие определения типов никогда не наследуют или не реализуют другие определения общего типа - они обычно реализуют открытые построенные типы, используя их собственные параметры типа с другими определениями общего типа.
Ответ Ripper234 показывает, как обрабатывать этот конкретный случай с помощью Reflection, поэтому я не буду его повторять; Я просто хотел уточнить отношения между этими типами, и я надеюсь, что это получилось, по крайней мере, несколько понятным.
Я думаю, что метод действительно не имеет смысла, потому что экземпляр никогда не относится к родовому типу - он всегда строится с определенным аргументом типа.
Другими словами, у вас никогда не может быть "открытой" переменной для назначения, а также ссылки на открытый экземпляр для использования в качестве значения для назначения.
Как вы говорите, вы не знаете, будут ли параметры типа одинаковыми - так (например) вы могли бы определить:
class BizarreList<T> : IList<int>
Похоже, что должен быть какой-то способ выражения отношений, хотя...
IList<>
когда-либо назначаться из List<>
? Я говорю «нет», потому что это может быть правдой только в том случае, если оба типа были созданы с общим аргументом типа, и, как мы уже указывали, аргумент типа не будет иметься. Это очень интересная проблема, хотя :)
Здесь метод расширения из AutoMapper:
public static bool IsCollectionType(this Type type)
{
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ICollection<>))
{
return true;
}
IEnumerable<Type> genericInterfaces = type.GetInterfaces().Where(t => t.IsGenericType);
IEnumerable<Type> baseDefinitions = genericInterfaces.Select(t => t.GetGenericTypeDefinition());
var isCollectionType = baseDefinitions.Any(t => t == typeof(ICollection<>));
return isCollectionType;
}