¿Cómo obtener el tipo de T de un miembro de una clase o método genérico?


675

Digamos que tengo un miembro genérico en una clase o método, entonces:

public class Foo<T>
{
    public List<T> Bar { get; set; }

    public void Baz()
    {
        // get type of T
    }   
}

Cuando una instancia de la clase, la Tconvierte MyTypeObject1, por lo que la clase tiene una propiedad de lista genérica: List<MyTypeObject1>. Lo mismo se aplica a un método genérico en una clase no genérica:

public class Foo
{
    public void Bar<T>()
    {
        var baz = new List<T>();

        // get type of T
    }
}

Me gustaría saber qué tipo de objetos contiene la lista de mi clase. Entonces, ¿la propiedad de la lista llamada Baro la variable local bazcontiene qué tipo de T?

No puedo hacerlo Bar[0].GetType(), porque la lista puede contener cero elementos. ¿Cómo puedo hacerlo?

Respuestas:


695

Si entiendo correctamente, su lista tiene el mismo parámetro de tipo que la clase contenedor. Si este es el caso, entonces:

Type typeParameterType = typeof(T);

Si tiene la suerte de tener objectcomo parámetro de tipo, consulte la respuesta de Marc .


44
Lol - sí, muy cierto; Supuse que el OP solo tenía object, IListo similar, pero esta podría ser la respuesta correcta.
Marc Gravell

32
Me encanta lo legible que typeofes. Si quieres saber el tipo de T, solo usa typeof(T):)
demoncodemonkey

2
De hecho, acabo de usar typeof (Type) y funciona muy bien.
Anton

1
Sin embargo, no puede usar typeof () con un parámetro genérico.
Reynevan

2
@Reynevan Por supuesto que puede usar typeof()con un parámetro genérico. ¿Tienes algún ejemplo donde no funcionaría? ¿O estás confundiendo parámetros de tipo y referencias?
Luaan

520

(Nota: Estoy asumiendo que todo lo que sabe es objecto IListo similares, y que la lista podría ser cualquier tipo en tiempo de ejecución)

Si sabes que es un List<T>, entonces:

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

Otra opción es mirar el indexador:

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

Usando nuevo TypeInfo:

using System.Reflection;
// ...
var type = abc.GetType().GetTypeInfo().GenericTypeArguments[0];

1
Tipo type = abc.GetType (). GetGenericArguments () [0]; ==> Índice de matriz fuera de límites ...
Patrick Desjardins

28
@Daok: entonces no es una Lista <T>
Marc Gravell

Necesita algo para BindingList o List o cualquier objeto que contenga un <T>. Lo que estoy haciendo utiliza un BindingListView personalizado <T>
Patrick Desjardins

1
Intente con BindingList <T>, nuestro BindingListView <T> hereda de BindingList <T> y ambos probé su opción y no funciona. Podría hacer algo mal ... pero creo que esta solución funciona para el tipo Lista <T> pero no para otro tipo de lista.
Patrick Desjardins

Tipo type = abc.GetType (). GetProperty ("Item"). PropertyType; return BindingListView <MyObject> en lugar de MyObject ...
Patrick Desjardins

49

Con el siguiente método de extensión puede escapar sin reflexión:

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

O más general:

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

Uso:

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

10
Esto solo es útil si ya conoce el tipo de Ten tiempo de compilación. En cuyo caso, realmente no necesita ningún código.
Recurrente

'return default (T);'
fantastory

1
@recursive: es útil si está trabajando con una lista de tipo anónimo.
JJJ

Hice exactamente lo mismo antes de leer su respuesta, pero llamé a la míaItemType
toddmo

31

Tratar

list.GetType().GetGenericArguments()

77
nueva Lista <int> () .GetType (). GetGenericArguments () devuelve System.Type [1] aquí con System.Int32 como entrada
Rauhotz

@Rauhotz, el GetGenericArgumentsmétodo devuelve un objeto Array Type, del cual debe analizar la posición del tipo genérico que necesita. Tales como Type<TKey, TValue>: necesitaría GetGenericArguments()[0]obtener el TKeytipo y GetGenericArguments()[1]obtener el TValuetipo
GoldBishop

14

Eso es trabajo para mi. Donde myList es algún tipo de lista desconocida.

IEnumerable myEnum = myList as IEnumerable;
Type entryType = myEnum.AsQueryable().ElementType;

1
Me sale un error que requiere un argumento de tipo (es decir <T>)
Joseph Humfrey

Joseph y otros, para deshacerse del error, está en System.Collections.
user2334883

1
Solo la segunda línea es necesaria para mí. A Listya es una implementación de IEnumerable, por lo que el elenco no parece agregar nada. Pero gracias, es una buena solución.
pipedreambomb

10

Si no necesita toda la variable Tipo y solo desea verificar el tipo, puede crear fácilmente una variable temporal y usar su operador.

T checkType = default(T);

if (checkType is MyClass)
{}

9

Puede usar este para el tipo de retorno de la lista genérica:

public string ListType<T>(T value)
{
    var valueType = value.GetType().GenericTypeArguments[0].FullName;
    return valueType;
}

8

Considere esto: lo uso para exportar 20 listas escritas de la misma manera:

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
Esto no funciona si T es una superclase abstracta de los objetos agregados reales. Sin mencionar, simplemente new T();haría lo mismo que (T)Activator.CreateInstance(typeof(T));. Requiere que agregue where T : new()a la definición de clase / función, pero si desea crear objetos, debe hacerlo de todos modos.
Nyerguds

Además, está llamando GetTypea una FirstOrDefaultentrada que resulta en una posible excepción de referencia nula. Si está seguro de que devolverá al menos un elemento, ¿por qué no utilizarlo First?
Mathias Lykkegaard Lorenzen

6

Utilizo este método de extensión para lograr algo similar:

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

Lo usas así:

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

Esto no es exactamente lo que está buscando, pero es útil para demostrar las técnicas involucradas.


Hola, gracias por tu respuesta, ¿podrías agregar la extensión para "StripStartingWith" también?
Cedric Arnould el

1
@CedricArnould - Agregado.
Ken Smith

5

El GetGenericArgument()método debe establecerse en el Tipo base de su instancia (cuya clase es una clase genérica myClass<T>). De lo contrario, devuelve un tipo [0]

Ejemplo:

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

5

Puede obtener el tipo de "T" de cualquier tipo de colección que implemente IEnumerable <T> con lo siguiente:

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

Tenga en cuenta que no admite tipos de colección que implementen IEnumerable <T> dos veces, p. Ej.

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

Supongo que podría devolver una variedad de tipos si necesita soportar esto ...

Además, es posible que también desee almacenar en caché el resultado por tipo de colección si lo hace mucho (por ejemplo, en un bucle).


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
Esto parece abordar la pregunta de si el tipo es una especie de lista, pero la pregunta es más sobre cómo determinar con qué parámetro de tipo genérico se inicializó un tipo que se sabe que es una Lista.
Nathan Tuggy

1

Usando la solución de 3dGrabber:

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

//and now 

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

0

Si desea conocer el tipo subyacente de una propiedad, intente esto:

propInfo.PropertyType.UnderlyingSystemType.GenericTypeArguments[0]

0

Así es como lo hice

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

Entonces llámalo así

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

O

var item = Activator.CreateInstance(Bar.GetType().GetElementType());

-9

Tipo:

type = list.AsEnumerable().SingleOrDefault().GetType();

1
Esto arrojaría una NullReferenceException si la lista no tiene elementos dentro de los que se pueda probar.
rossisdead

1
SingleOrDefault()También lanza InvalidOperationExceptioncuando hay dos o más elementos.
devgeezer

Esta respuesta es incorrecta, como lo señalaron correctamente \ @rossisdead y \ @devgeezer.
Oliver
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.