Как получить тип T от члена универсального класса или метода

Скажем, у меня есть общий член в классе или методе, например:

public class Foo<T>
{
    public List<T> Bar { get; set; }
    
    public void Baz()
    {
        // get type of T
    }   
}

Когда я создаю экземпляр класса, T становится MyTypeObject1, поэтому у класса есть общее свойство списка: List<MyTypeObject1>. То же самое относится к универсальному методу в неуниверсальном классе:

public class Foo
{
    public void Bar<T>()
    {
        var baz = new List<T>();
        
        // get type of T
    }
}

Я хотел бы знать, какой тип объектов содержит список моего класса. Итак, какой тип T содержит свойство списка с именем Bar или локальная переменная baz?

Я не могу сделать Bar[0].GetType(), потому что список может содержать ноль элементов. Как я могу это сделать?


person Patrick Desjardins    schedule 17.02.2009    source источник


Ответы (16)


Если я правильно понимаю, ваш список имеет тот же параметр типа, что и сам класс контейнера. Если это так, то:

Type typeParameterType = typeof(T);

Если вам посчастливилось иметь object в качестве параметра типа, см. ответ Марка.

person Tamas Czinege    schedule 17.02.2009
comment
Мне нравится читабельность typeof. Если вы хотите узнать тип T, просто используйте typeof(T) :) - person demoncodemonkey; 17.02.2012
comment
На самом деле я просто использовал typeof (Type), и он отлично работает. - person Anton; 26.06.2017
comment
Однако вы не можете использовать typeof () с универсальным параметром. - person Reynevan; 08.03.2019
comment
@Reynevan Конечно, вы можете использовать typeof() с универсальным параметром. У вас есть пример, когда это не сработало бы? Или вы путаете параметры типов и ссылки? - person Luaan; 26.07.2019

(примечание: я предполагаю, что все, что вы знаете, это object, IList или аналогичные, и что список может быть любого типа во время выполнения)

Если вы знаете, что это List<T>, тогда:

Type type = abc.GetType().GetGenericArguments()[0];

Другой вариант - посмотреть индексатор:

Type type = abc.GetType().GetProperty("Item").PropertyType;

Использование нового TypeInfo:

using System.Reflection;
// ...
var type = abc.GetType().GetTypeInfo().GenericTypeArguments[0];
person Marc Gravell    schedule 17.02.2009
comment
Введите type = abc.GetType (). GetGenericArguments () [0]; == ›Индекс массива вне границ ... - person Patrick Desjardins; 17.02.2009
comment
@Daok: тогда это не список ‹T› - person Marc Gravell; 17.02.2009
comment
Нужно что-то для BindingList или List или любого другого объекта, который содержит ‹T›. То, что я делаю, использую настраиваемый BindingListView ‹T› - person Patrick Desjardins; 17.02.2009
comment
Попробуйте BindingList ‹T›, наш BindingListView ‹T› наследуется от BindingList ‹T›, и я попробовал оба варианта, но он не работает. Я могу сделать что-то не так ... но я думаю, что это решение работает для типа List ‹T›, но не для других типов списков. - person Patrick Desjardins; 17.02.2009
comment
Введите type = abc.GetType (). GetProperty (Item) .PropertyType; вернуть BindingListView ‹MyObject› вместо MyObject ... - person Patrick Desjardins; 17.02.2009
comment
Я попытаюсь создать фрагмент кода, который воспроизводит это. Может быть сегодня вечером или завтра. - person Patrick Desjardins; 17.02.2009
comment
Небольшое дополнение: GetProperty (Item) вызовет исключение System.Reflection.AmbiguousMatchException в (редком?) Случае, если имеется более одного индексатора. Я обрабатываю этот случай с помощью try..catch: попробуйте GetProperty, в противном случае вернитесь к GetGenericArguments. - person Michael Stum; 08.12.2009
comment
Думаю, вторая форма не работает с обфускацией. В любом случае спасибо за ответ, это именно то, что я искал. - person greenoldman; 19.10.2010
comment
Своеобразный. Это просто возвращает для меня фиктивный тип T. - person Nyerguds; 02.12.2015
comment
@Nyerguds, что обычно означает, что вы смотрите на открытый общий тип, то есть List<>, а не List<Foo>; или T от внешнего типа. - person Marc Gravell; 02.12.2015
comment
@MarcGravell Действительно. В конце концов я получил это. Я просматривал родительские классы с помощью цикла while, чтобы получить тип, поскольку класс объекта actual был дочерним классом универсального типа, внутренний тип которого я искал. Думаю, я зашел на один шаг слишком далеко вверх по иерархии, и он перешел от GenericType<ObjectType>, который был мне нужен, до GenericType<T>. - person Nyerguds; 03.12.2015
comment
В одном случае мне потребовался запрос индексатора: у меня был неуниверсальный класс, унаследованный от List<int>, и я хотел найти тип элемента int. Поскольку у самого класса не было общих аргументов, индексатор был единственным способом узнать. - person Ray; 19.11.2016

С помощью следующего метода расширения вы можете обойтись без размышлений:

public static Type GetListType<T>(this List<T> _)
{
    return typeof(T);
}

Или более общий:

public static Type GetEnumeratedType<T>(this IEnumerable<T> _)
{
    return typeof(T);
}

Использование:

List<string>        list    = new List<string> { "a", "b", "c" };
IEnumerable<string> strings = list;
IEnumerable<object> objects = list;

Type listType    = list.GetListType();           // string
Type stringsType = strings.GetEnumeratedType();  // string
Type objectsType = objects.GetEnumeratedType();  // BEWARE: object
person 3dGrabber    schedule 23.09.2012
comment
Это полезно только в том случае, если вы уже знаете тип T во время компиляции. В этом случае вам вообще не нужен код. - person recursive; 12.02.2015
comment
@recursive: это полезно, если вы работаете со списком анонимного типа. - person JJJ; 17.06.2016

Пытаться

list.GetType().GetGenericArguments()
person Rauhotz    schedule 17.02.2009
comment
new List ‹int› () .GetType (). GetGenericArguments () возвращает здесь System.Type [1] с System.Int32 в качестве записи - person Rauhotz; 17.02.2009
comment
@Rauhotz метод GetGenericArguments возвращает объект массива Type, из которого вам нужно затем проанализировать позицию нужного вам универсального типа. Например, Type<TKey, TValue>: вам нужно GetGenericArguments()[0], чтобы получить тип TKey и GetGenericArguments()[1], чтобы получить тип TValue - person GoldBishop; 18.04.2018

Следующее работает для меня. Где myList - это какой-то неизвестный список.

IEnumerable myEnum = myList as IEnumerable;
Type entryType = myEnum.AsQueryable().ElementType;
person Carlos Rodriguez    schedule 18.05.2011
comment
Я получаю сообщение об ошибке, требующее аргумента типа (например, <T>) - person Joseph Humfrey; 21.05.2015
comment
Джозеф и другие, чтобы избавиться от ошибки, она находится в System.Collections. - person user2334883; 16.01.2016
comment
Мне нужна как раз вторая строчка. List уже является реализацией IEnumerable, поэтому приведение ничего не добавляет. Но спасибо, это хорошее решение. - person pipedreambomb; 22.11.2016

Если вам не нужна вся переменная Type и вы просто хотите проверить тип, вы можете легко создать временную переменную и использовать оператор is.

T checkType = default(T);

if (checkType is MyClass)
{}
person Sebi    schedule 08.05.2015
comment
Это должен быть принятый ответ, безусловно, самый эффективный. - person Serj Sagan; 05.11.2020
comment
Кодируйте как профессионал :) - person Ebrahim Karimi; 14.03.2021
comment
@EbrahimKarimi Конечно :-) - person Sebi; 14.03.2021

Вы можете использовать его для типа возвращаемого значения общего списка:

public string ListType<T>(T value)
{
    var valueType = value.GetType().GenericTypeArguments[0].FullName;
    return valueType;
}
person vishal kumar Saxena    schedule 05.01.2017

Учти это:

Я использую его для экспорта 20 типизированных списков таким же образом:

private void Generate<T>()
{
    T item = (T)Activator.CreateInstance(typeof(T));

    ((T)item as DemomigrItemList).Initialize();

    Type type = ((T)item as DemomigrItemList).AsEnumerable().FirstOrDefault().GetType();
    if (type == null) 
        return;
    if (type != typeof(account)) // Account is listitem in List<account>
    {
        ((T)item as DemomigrItemList).CreateCSV(type);
    }
}
person Ferenc Mucsi    schedule 29.03.2010
comment
Это не работает, если T является абстрактным суперклассом фактически добавленных объектов. Не говоря уже о том, что просто new T(); будет делать то же самое, что и (T)Activator.CreateInstance(typeof(T));. Это действительно требует, чтобы вы добавили where T : new() в определение класса / функции, но если вы хотите создать объекты, это должно быть сделано в любом случае. - person Nyerguds; 11.07.2013
comment
Кроме того, вы вызываете GetType для записи FirstOrDefault, что приводит к потенциальному исключению нулевой ссылки. Если вы уверены, что он вернет хотя бы один элемент, почему бы вместо этого не использовать First? - person Mathias Lykkegaard Lorenzen; 16.03.2015

Я использую этот метод расширения для чего-то подобного:

public static string GetFriendlyTypeName(this Type t)
{
    var typeName = t.Name.StripStartingWith("`");
    var genericArgs = t.GetGenericArguments();
    if (genericArgs.Length > 0)
    {
        typeName += "<";
        foreach (var genericArg in genericArgs)
        {
            typeName += genericArg.GetFriendlyTypeName() + ", ";
        }
        typeName = typeName.TrimEnd(',', ' ') + ">";
    }
    return typeName;
}

public static string StripStartingWith(this string s, string stripAfter)
{
    if (s == null)
    {
        return null;
    }
    var indexOf = s.IndexOf(stripAfter, StringComparison.Ordinal);
    if (indexOf > -1)
    {
        return s.Substring(0, indexOf);
    }
    return s;
}

Вы используете это так:

[TestMethod]
public void GetFriendlyTypeName_ShouldHandleReallyComplexTypes()
{
    typeof(Dictionary<string, Dictionary<string, object>>).GetFriendlyTypeName()
        .ShouldEqual("Dictionary<String, Dictionary<String, Object>>");
}

Это не совсем то, что вы ищете, но это полезно для демонстрации задействованных методов.

person Ken Smith    schedule 28.08.2014
comment
Объяснение было бы в порядке. Например, в чем его суть? В чем идея? Пожалуйста, ответьте, отредактировав свой ответ, а не здесь, в комментариях (без Edit :, Update: и т.п. - ответ должен выглядеть так, как если бы он был написан сегодня). - person Peter Mortensen; 21.06.2021

Вы можете получить тип «T» из любого типа коллекции, который реализует IEnumerable ‹T›, со следующим:

public static Type GetCollectionItemType(Type collectionType)
{
    var types = collectionType.GetInterfaces()
        .Where(x => x.IsGenericType 
            && x.GetGenericTypeDefinition() == typeof(IEnumerable<>))
        .ToArray();
    // Only support collections that implement IEnumerable<T> once.
    return types.Length == 1 ? types[0].GetGenericArguments()[0] : null;
}

Обратите внимание, что он не поддерживает типы коллекций, которые реализуют IEnumerable ‹T› дважды, например

public class WierdCustomType : IEnumerable<int>, IEnumerable<string> { ... }

Я полагаю, вы могли бы вернуть массив типов, если бы вам нужно было это поддержать ...

Кроме того, вы также можете захотеть кэшировать результат для каждого типа коллекции, если вы делаете это много (например, в цикле).

person Dan Malcolm    schedule 16.04.2014

Метод GetGenericArgument() должен быть установлен для базового типа вашего экземпляра (класс которого является универсальным классом myClass<T>). В противном случае он возвращает тип [0].

Пример:

Myclass<T> instance = new Myclass<T>();
Type[] listTypes = typeof(instance).BaseType.GetGenericArguments();
person Thomas    schedule 29.07.2010

Использование решение 3dGrabber:

public static T GetEnumeratedType<T>(this IEnumerable<T> _)
{
    return default(T);
}

//and now

var list = new Dictionary<string, int>();
var stronglyTypedVar = list.GetEnumeratedType();
person fantastory    schedule 03.06.2015

Если вы хотите узнать базовый тип свойства, попробуйте следующее:

propInfo.PropertyType.UnderlyingSystemType.GenericTypeArguments[0]
person Fatih Çelik    schedule 13.10.2016

Вот как я это сделал:

internal static Type GetElementType(this Type type)
{
    // Use type.GenericTypeArguments if it exists
    if (type.GenericTypeArguments.Any())
        return type.GenericTypeArguments.First();

    return type.GetRuntimeProperty("Item").PropertyType);
}

Тогда назовите это так:

var item = Activator.CreateInstance(iListType.GetElementType());

Or

var item = Activator.CreateInstance(Bar.GetType().GetElementType());
person Alen.Toma    schedule 27.06.2017

Тип:

type = list.AsEnumerable().SingleOrDefault().GetType();
person Ferenc Mucsi    schedule 07.03.2010
comment
Это вызовет исключение NullReferenceException, если в списке нет элементов для проверки. - person rossisdead; 14.07.2010
comment
SingleOrDefault() также бросает InvalidOperationException, когда есть два или более элементов. - person devgeezer; 14.03.2012
comment
Этот ответ неверен, как правильно указали \ @rossisdead и \ @devgeezer. - person Oliver; 23.01.2013

person    schedule
comment
Это, по-видимому, решает вопрос о том, является ли тип чем-то вроде списка, но вопрос больше о том, как определить, с каким параметром универсального типа уже был инициализирован тип, который, как известно, является списком. - person Nathan Tuggy; 06.05.2015
comment
Объяснение было бы в порядке. Например, в чем его суть? В чем идея? Пожалуйста, ответьте, отредактировав свой ответ, а не здесь, в комментариях (без Edit :, Update: или подобное - ответ должен появиться так, как если бы он был написан сегодня). - person Peter Mortensen; 21.06.2021