получение типа T из IEnumerable <T>

106

есть ли способ получить тип TизIEnumerable<T> посредством отражения?

например

у меня есть переменная IEnumerable<Child>информация; я хочу получить тип ребенка через отражение

Усман Масуд
источник
1
В каком контексте? Что это за IEnumerable <T>? Это экземпляр объекта, отправленный в качестве аргумента? Или что?
Мехрдад Афшари

Ответы:

142
IEnumerable<T> myEnumerable;
Type type = myEnumerable.GetType().GetGenericArguments()[0]; 

Таким образом,

IEnumerable<string> strings = new List<string>();
Console.WriteLine(strings.GetType().GetGenericArguments()[0]);

печатает System.String.

См. MSDN для Type.GetGenericArguments.

Изменить: я считаю, что это решит проблемы в комментариях:

// returns an enumeration of T where o : IEnumerable<T>
public IEnumerable<Type> GetGenericIEnumerables(object o) {
    return o.GetType()
            .GetInterfaces()
            .Where(t => t.IsGenericType
                && t.GetGenericTypeDefinition() == typeof(IEnumerable<>))
            .Select(t => t.GetGenericArguments()[0]);
}

Некоторые объекты реализуют более одного универсального IEnumerable поэтому необходимо возвращать их перечисление.

Изменить: хотя, я должен сказать, ужасная идея для класса реализовать IEnumerable<T>более одного T.

Джейсон
источник
Или, что еще хуже, напишите метод с возвратами yield и попытайтесь вызвать GetType для переменной, созданной этим методом. Он скажет вам, что это не событие общего типа. По сути, не существует универсального способа получить T с учетом переменной экземпляра типа IEnumerable <T>
Дарин Димитров
1
Или попробуйте с классом MyClass: IEnumerable <int> {}. У этого класса нет универсального интерфейса.
Стефан Штайнеггер,
1
Зачем кому-либо прибегать к получению общих аргументов, а затем к получению типа из его индексатора? Это просто напрашивается на катастрофу, особенно когда язык поддерживает typeof (T), как предлагает @amsprich в своем ответе, который также может использоваться как с общим, так и с известным типом ...
Роберт Петц
Это ужасно не работает при использовании с запросами linq - первый общий аргумент WhereSelectEnumerableIterator - нет . Вы получаете общий аргумент базового объекта, а не сам интерфейс.
Pxtl
myEnumerable.GetType (). GetGenericArguments () [0] дает вам свойство FullName, которое сообщает вам namespace.classname. Если вы ищете только имя класса, используйте myEnumerable.GetType (). GetGenericArguments () [0] .Name
user5534263 08
38

Я бы просто сделал способ расширения. Это сработало со всем, что я на него бросил.

public static Type GetItemType<T>(this IEnumerable<T> enumerable)
{
    return typeof(T);
}
Амсприх
источник
6
Это не сработает, если ваша ссылка на время компиляции относится только к типу объекта.
Stijn Van Antwerpen
27

У меня была похожая проблема. Выбранный ответ работает для реальных экземпляров. В моем случае у меня был только тип (изPropertyInfo ).

Выбранный ответ не выполняется, если сам тип typeof(IEnumerable<T>)не является реализациейIEnumerable<T> .

В этом случае работает следующее:

public static Type GetAnyElementType(Type type)
{
   // Type is Array
   // short-circuit if you expect lots of arrays 
   if (type.IsArray)
      return type.GetElementType();

   // type is IEnumerable<T>;
   if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof (IEnumerable<>))
      return type.GetGenericArguments()[0];

   // type implements/extends IEnumerable<T>;
   var enumType = type.GetInterfaces()
                           .Where(t => t.IsGenericType && 
                                  t.GetGenericTypeDefinition() == typeof(IEnumerable<>))
                           .Select(t => t.GenericTypeArguments[0]).FirstOrDefault();
   return enumType ?? type;
}
Эли Альгранти
источник
Спас мой день. В моем случае я добавил отдельный оператор if для обработки строк, поскольку он реализует IEnumerable <char>
Эдмунд П. Чарумбира,
Type.GenericTypeArguments- только для dotNet FrameWork версии> = 4.5. В противном случае - используйте Type.GetGenericArgumentsвместо этого.
Кое Кто
20

Если вы знаете IEnumerable<T>(через дженерики), тогда просто typeof(T)должно работать. В противном случае (для objectили неуниверсального IEnumerable) проверьте реализованные интерфейсы:

        object obj = new string[] { "abc", "def" };
        Type type = null;
        foreach (Type iType in obj.GetType().GetInterfaces())
        {
            if (iType.IsGenericType && iType.GetGenericTypeDefinition()
                == typeof(IEnumerable<>))
            {
                type = iType.GetGenericArguments()[0];
                break;
            }
        }
        if (type != null) Console.WriteLine(type);
Марк Гравелл
источник
3
Некоторые объекты реализуют более одного универсального IEnumerable.
Джейсон
5
@Jason - и в таких случаях вопрос «найди букву Т» - уже сомнительный вопрос; Я ничего не могу с этим поделать ...
Марк Грейвелл
Один маленький Гоча для тех , кто пытается использовать это с Type typeпараметром , а не как object objпараметр , вы не можете просто заменить obj.GetType()с , typeпотому что если вы передаете typeof(IEnumerable<T>)вы ничего не получите. Чтобы обойти это, проверьте typeсам, чтобы увидеть, является ли он общим, IEnumerable<>а затем его интерфейсы.
Ян Мерсер,
8

Большое спасибо за обсуждение. Я использовал его как основу для решения ниже, которое хорошо работает во всех интересующих меня случаях (IEnumerable, производные классы и т. Д.). Думал, что я должен поделиться здесь, если кому-то это тоже понадобится:

  Type GetItemType(object someCollection)
  {
    var type = someCollection.GetType();
    var ienum = type.GetInterface(typeof(IEnumerable<>).Name);
    return ienum != null
      ? ienum.GetGenericArguments()[0]
      : null;
  }
Бернардо
источник
Вот однострочник, который делает все это с помощью условного оператора NULL: someCollection.GetType().GetInterface(typeof(IEnumerable<>).Name)?.GetGenericArguments()?.FirstOrDefault()
Mass Dot Net
2

Просто используйте typeof(T)

РЕДАКТИРОВАТЬ: Или используйте .GetType (). GetGenericParameter () для созданного объекта, если у вас нет T.

сдерживать
источник
У тебя не всегда есть Т.
Джейсон
Правда, в этом случае вы можете использовать .GetType (). Я изменю свой ответ.
Обуздать
2

Альтернатива для более простых ситуаций, когда будет либо IEnumerable<T>или T- обратите внимание на использование GenericTypeArgumentsвместо вместо GetGenericArguments().

Type inputType = o.GetType();
Type genericType;
if ((inputType.Name.StartsWith("IEnumerable"))
    && ((genericType = inputType.GenericTypeArguments.FirstOrDefault()) != null)) {

    return genericType;
} else {
    return inputType;
}
Роб Черч
источник
1

Это улучшение решения Eli Algranti в том, что оно также будет работать там, где IEnumerable<> тип находится на любом уровне в дереве наследования.

Это решение получит тип элемента из любого Type. Если тип не является IEnumerable<>, он вернет переданный тип. Для объектов используйте GetType. Для типов используйте typeof, а затем вызовите этот метод расширения для результата.

public static Type GetGenericElementType(this Type type)
{
    // Short-circuit for Array types
    if (typeof(Array).IsAssignableFrom(type))
    {
        return type.GetElementType();
    }

    while (true)
    {
        // Type is IEnumerable<T>
        if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>))
        {
            return type.GetGenericArguments().First();
        }

        // Type implements/extends IEnumerable<T>
        Type elementType = (from subType in type.GetInterfaces()
            let retType = subType.GetGenericElementType()
            where retType != subType
            select retType).FirstOrDefault();

        if (elementType != null)
        {
            return elementType;
        }

        if (type.BaseType == null)
        {
            return type;
        }

        type = type.BaseType;
    }
}
Нео
источник
1

Я знаю, что это немного устарело, но я считаю, что этот метод покроет все проблемы и проблемы, указанные в комментариях. Благодарим Эли Альгранти за вдохновение в моей работе.

/// <summary>Finds the type of the element of a type. Returns null if this type does not enumerate.</summary>
/// <param name="type">The type to check.</param>
/// <returns>The element type, if found; otherwise, <see langword="null"/>.</returns>
public static Type FindElementType(this Type type)
{
   if (type.IsArray)
      return type.GetElementType();

   // type is IEnumerable<T>;
   if (ImplIEnumT(type))
      return type.GetGenericArguments().First();

   // type implements/extends IEnumerable<T>;
   var enumType = type.GetInterfaces().Where(ImplIEnumT).Select(t => t.GetGenericArguments().First()).FirstOrDefault();
   if (enumType != null)
      return enumType;

   // type is IEnumerable
   if (IsIEnum(type) || type.GetInterfaces().Any(IsIEnum))
      return typeof(object);

   return null;

   bool IsIEnum(Type t) => t == typeof(System.Collections.IEnumerable);
   bool ImplIEnumT(Type t) => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>);
}
Dahall
источник
1
public static Type GetInnerGenericType(this Type type)
{
  // Attempt to get the inner generic type
  Type innerType = type.GetGenericArguments().FirstOrDefault();

  // Recursively call this function until no inner type is found
  return innerType is null ? type : innerType.GetInnerGenericType();
}

Это рекурсивная функция, которая сначала просматривает список универсальных типов, пока не получит определение конкретного типа без внутренних универсальных типов.

Я тестировал этот метод с таким типом: ICollection<IEnumerable<ICollection<ICollection<IEnumerable<IList<ICollection<IEnumerable<IActionResult>>>>>>>>

который должен вернуться IActionResult

Тайлер Хаскинс
источник
0

typeof(IEnumerable<Foo>). вернет первый общий аргумент - в этом случае .GetGenericArguments()[0]typeof(Foo)

Даниэль Брюкнер
источник
0

вот как я обычно это делаю (с помощью метода расширения):

public static Type GetIEnumerableUnderlyingType<T>(this T iEnumerable)
    {
        return typeof(T).GetTypeInfo().GetGenericArguments()[(typeof(T)).GetTypeInfo().GetGenericArguments().Length - 1];
    }
H7O
источник
0

Вот моя нечитаемая версия выражения запроса Linq.

public static Type GetEnumerableType(this Type t) {
    return !typeof(IEnumerable).IsAssignableFrom(t) ? null : (
    from it in (new[] { t }).Concat(t.GetInterfaces())
    where it.IsGenericType
    where typeof(IEnumerable<>)==it.GetGenericTypeDefinition()
    from x in it.GetGenericArguments() // x represents the unknown
    let b = it.IsConstructedGenericType // b stand for boolean
    select b ? x : x.BaseType).FirstOrDefault()??typeof(object);
}

Обратите внимание, что метод также принимает IEnumerableво внимание неуниверсальный тип , objectв этом случае он возвращается , потому что он принимает в Typeкачестве аргумента не конкретный экземпляр, а. Кстати, поскольку x представляет неизвестное , я нашел это видео интересным, хотя и неуместным.

Кен Кин
источник