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

675

Допустим, у меня есть универсальный член в классе или методе, поэтому:

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
    }
}

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

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

Патрик Дежарден
источник

Ответы:

695

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

Type typeParameterType = typeof(T);

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

Тамас Чинеге
источник
4
Лол - да, очень верно; Я предполагал, что ОП только имел object, IListили похожий - но это вполне может быть правильный ответ.
Марк Гравелл
32
Я люблю, как читается typeof. Если вы хотите узнать тип T, просто используйте typeof(T):)
demoncodemonkey
2
Я на самом деле просто использовал typeof (Type), и он прекрасно работает.
Антон
1
Вы не можете использовать typeof () с общим параметром.
Рейневан,
2
@Reynevan Конечно, вы можете использовать typeof()с универсальным параметром. У вас есть пример, где это не сработает? Или вы путаете параметры типа и ссылки?
Луаан
520

(примечание: я предполагаю, что все, что вы знаете, является 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];
Марк Гравелл
источник
1
Тип type = abc.GetType (). GetGenericArguments () [0]; ==> Индекс массива вне границ ...
Патрик Дежарден
28
@Daok: тогда это не Список <T>
Марк Гравелл
Нужно что-то для BindingList или List или любого другого объекта, который содержит <T>. То, что я делаю, использует пользовательский BindingListView <T>
Патрик Дежарден
1
Попробуйте BindingList <T>, наш BindingListView <T> унаследован от BindingList <T>, и оба я пробовал оба варианта, но он не работает. Я мог бы сделать что-то не так ... но я думаю, что это решение работает для типа List <T>, но не для другого типа списка.
Патрик Дежарден
Тип type = abc.GetType (). GetProperty ("Item"). PropertyType; вернуть BindingListView <MyObject> вместо MyObject ...
Патрик Дежарден
49

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

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
3dGrabber
источник
10
Это полезно, только если вы уже знаете тип Tво время компиляции. В этом случае вам вообще не нужен код.
рекурсивный
'вернуть по умолчанию (T);'
фантастика
1
@recursive: это полезно, если вы работаете со списком анонимного типа.
JJJ
Я сделал то же самое, прежде чем я прочитал ваш ответ, но я назвал свойItemType
toddmo
31

Пытаться

list.GetType().GetGenericArguments()
Rauhotz
источник
7
new List <int> (). GetType (). GetGenericArguments () возвращает System.Type [1] здесь с System.Int32 в качестве записи
Rauhotz
@Rauhotz GetGenericArgumentsметод возвращает объект Array Type, из которого вам необходимо проанализировать положение необходимого вам универсального типа. Например Type<TKey, TValue>: вам нужно GetGenericArguments()[0]получить TKeyтип и GetGenericArguments()[1]получить TValueтип
GoldBishop
14

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

IEnumerable myEnum = myList as IEnumerable;
Type entryType = myEnum.AsQueryable().ElementType;
Карлос Родригес
источник
1
Я получаю сообщение об ошибке, что требуется аргумент типа (то есть <T>)
Джозеф Хамфри
Джозеф и другие, чтобы избавиться от ошибки именно в System.Collections.
user2334883
1
Мне нужна только вторая строка. A Listуже является реализацией IEnumerable, так что, похоже, актеры ничего не добавляют. Но спасибо, это хорошее решение.
pipedreambomb
10

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

T checkType = default(T);

if (checkType is MyClass)
{}
Sebi
источник
9

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

public string ListType<T>(T value)
{
    var valueType = value.GetType().GenericTypeArguments[0].FullName;
    return valueType;
}
Вишал Кумар Саксена
источник
8

Учтите это: я использую его для экспорта 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);
    }
}
Ференц Мучи
источник
1
Это не работает, если T является абстрактным суперклассом фактически добавленных объектов. Не говоря уже о том, new T();чтобы сделать то же самое, что и (T)Activator.CreateInstance(typeof(T));. Это требует, чтобы вы добавили where T : new()определение класса / функции, но если вы хотите создавать объекты, это должно быть сделано в любом случае.
Nyerguds
Кроме того , вы вызываете GetTypeна FirstOrDefaultвход , в результате потенциального эталонного нулевого исключения. Если вы уверены, что он вернет хотя бы один элемент, почему бы не использовать Firstвместо этого?
Матиас Ликкегор Лоренцен
6

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

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>>");
}

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

Кен Смит
источник
Привет, спасибо за ваш ответ, не могли бы вы добавить расширение для "StripStartingWith"?
Седрик
1
@CedricArnould - Добавлено.
Кен Смит
5

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

Пример:

Myclass<T> instance = new Myclass<T>();
Type[] listTypes = typeof(instance).BaseType.GetGenericArguments();
Томас
источник
5

Вы можете получить тип «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> { ... }

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

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

Дэн Малкольм
источник
1
public bool IsCollection<T>(T value){
  var valueType = value.GetType();
  return valueType.IsArray() || typeof(IEnumerable<object>).IsAssignableFrom(valueType) || typeof(IEnumerable<T>).IsAssignableFrom(valuetype);
}
Каранвир Канг
источник
1
Похоже, это решает вопрос о том, является ли тип списочным, но вопрос скорее в том, как определить, с каким параметром универсального типа тип, который, как известно, является списком, уже был инициализирован.
Натан Тагги
1

Используя решение 3dGrabber:

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

//and now 

var list = new Dictionary<string, int>();
var stronglyTypedVar = list.GetEnumeratedType();
fantastory
источник
0

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

propInfo.PropertyType.UnderlyingSystemType.GenericTypeArguments[0]
Фатих Челик
источник
0

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

internal static Type GetElementType(this Type type)
{
        //use type.GenericTypeArguments if exist 
        if (type.GenericTypeArguments.Any())
         return type.GenericTypeArguments.First();

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

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

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

ИЛИ

var item = Activator.CreateInstance(Bar.GetType().GetElementType());
Alen.Toma
источник
-9

Тип:

type = list.AsEnumerable().SingleOrDefault().GetType();
Ференц Мучи
источник
1
Это вызовет исключение NullReferenceException, если список не имеет элементов внутри него, чтобы его можно было проверить.
rossisdead
1
SingleOrDefault()также бросает, InvalidOperationExceptionкогда есть два или более элемента.
devgeezer
Этот ответ неверен, как правильно указали \ @rossisdead и \ @devgeezer.
Оливер