¿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?

Patrick Desjardins
fuente

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 .

Tamas Czinege
fuente
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];
Marc Gravell
fuente
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
3dGrabber
fuente
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()
Rauhotz
fuente
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;
Carlos Rodriguez
fuente
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)
{}
Sebi
fuente
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;
}
vishal kumar Saxena
fuente
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);
    }
}
Ferenc Mucsi
fuente
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.

Ken Smith
fuente
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();
Thomas
fuente
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).

Dan Malcolm
fuente
1
public bool IsCollection<T>(T value){
  var valueType = value.GetType();
  return valueType.IsArray() || typeof(IEnumerable<object>).IsAssignableFrom(valueType) || typeof(IEnumerable<T>).IsAssignableFrom(valuetype);
}
Karanvir Kang
fuente
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();
fantastory
fuente
0

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

propInfo.PropertyType.UnderlyingSystemType.GenericTypeArguments[0]
Fatih Çelik
fuente
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());
Alen.Toma
fuente
-9

Tipo:

type = list.AsEnumerable().SingleOrDefault().GetType();
Ferenc Mucsi
fuente
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