mendapatkan tipe T dari IEnumerable <T>

106

apakah ada cara untuk mengambil tipe T dari IEnumerable<T>melalui refleksi?

misalnya

saya punya IEnumerable<Child>info variabel ; Saya ingin mendapatkan kembali tipe Child melalui refleksi

Usman Masood
sumber
1
Dalam konteks apa? Apa ini IEnumerable <T>? Apakah itu contoh objek dikirim sebagai argumen? Atau apa?
Mehrdad Afshari

Jawaban:

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

Demikian,

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

cetakan System.String .

Lihat MSDN untuk Type.GetGenericArguments.

Sunting: Saya yakin ini akan mengatasi masalah di komentar:

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

Beberapa objek mengimplementasikan lebih dari satu generik IEnumerablesehingga perlu mengembalikan enumerasi mereka.

Sunting: Meskipun, saya harus mengatakan, itu adalah ide yang buruk bagi kelas untuk diterapkan IEnumerable<T>lebih dari satu T.

jason
sumber
Atau lebih buruk lagi, tulis metode dengan pengembalian hasil dan coba panggil GetType pada variabel yang dibuat dengan metode ini. Ini akan memberi tahu Anda bahwa ini bukan acara tipe generik. Jadi pada dasarnya tidak ada cara universal untuk mendapatkan T diberi variabel contoh tipe IEnumerable <T>
Darin Dimitrov
1
Atau coba dengan kelas MyClass: IEnumerable <int> {}. Kelas ini tidak memiliki antarmuka umum.
Stefan Steinegger
1
Mengapa ada orang yang menggunakan argumen umum dan kemudian mengambil tipe dari pengindeksnya? Itu hanya meminta bencana, terutama ketika bahasanya mendukung tipe (T) seperti yang disarankan @amsprich dalam jawabannya, yang juga dapat digunakan dengan tipe generik atau yang dikenal ...
Robert Petz
Ini gagal total saat digunakan dengan kueri linq - argumen umum pertama dari WhereSelectEnumerableIterator tidak . Anda mendapatkan argumen umum dari objek yang mendasari, bukan antarmuka itu sendiri.
Pxtl
myEnumerable.GetType (). GetGenericArguments () [0] memberi Anda properti FullName yang memberi tahu Anda namespace.classname. Jika Anda hanya mencari nama kelas, gunakan myEnumerable.GetType (). GetGenericArguments () [0] .Name
user5534263
38

Saya baru saja membuat metode ekstensi. Ini bekerja dengan semua yang saya lemparkan.

public static Type GetItemType<T>(this IEnumerable<T> enumerable)
{
    return typeof(T);
}
amsprich
sumber
6
Ini tidak akan berfungsi jika referensi waktu kompilasi Anda hanya berupa objek tipe.
Stijn Van Antwerpen
27

Saya memiliki masalah serupa. Jawaban yang dipilih berfungsi untuk contoh aktual. Dalam kasus saya, saya hanya memiliki tipe (dari a PropertyInfo).

Jawaban yang dipilih gagal ketika tipe itu sendiri typeof(IEnumerable<T>)bukan merupakan implementasi dari IEnumerable<T>.

Untuk kasus ini pekerjaan berikut:

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;
}
Eli Algranti
sumber
Menyelamatkan hariku. Untuk kasus saya, saya menambahkan pernyataan if terpisah untuk menangani string karena mengimplementasikan IEnumerable <char>
Edmund P Charumbira
Type.GenericTypeArguments- hanya untuk dotNet FrameWork versi> = 4.5. Jika tidak - gunakan Type.GetGenericArgumentssaja.
Кое Кто
20

Jika Anda tahu IEnumerable<T>(melalui generik), maka typeof(T)seharusnya berfungsi. Jika tidak (untuk object, atau non-generik IEnumerable), periksa antarmuka yang diterapkan:

        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);
Marc Gravell
sumber
3
Beberapa objek menerapkan lebih dari satu IEnumerable generik.
jason
5
@Jason - dan dalam kasus tersebut, pertanyaan tentang "temukan T" sudah menjadi pertanyaan yang meragukan; Saya tidak bisa berbuat apa-apa tentang itu ...
Marc Gravell
Satu gotcha kecil untuk siapa pun yang mencoba menggunakan ini dengan Type typeparameter daripada object objparameter: Anda tidak bisa hanya mengganti obj.GetType()dengan typekarena jika Anda meneruskan typeof(IEnumerable<T>)Anda tidak mendapatkan apa-apa. Untuk mengatasi ini, uji typedirinya sendiri untuk melihat apakah itu generik IEnumerable<>dan kemudian antarmukanya.
Ian Mercer
8

Terima kasih banyak atas pembahasannya. Saya menggunakannya sebagai dasar untuk solusi di bawah ini, yang berfungsi dengan baik untuk semua kasus yang menarik bagi saya (IEnumerable, kelas turunan, dll). Saya pikir saya harus berbagi di sini jika ada yang membutuhkannya juga:

  Type GetItemType(object someCollection)
  {
    var type = someCollection.GetType();
    var ienum = type.GetInterface(typeof(IEnumerable<>).Name);
    return ienum != null
      ? ienum.GetGenericArguments()[0]
      : null;
  }
Bernardo
sumber
Berikut ini satu baris yang melakukan semua ini menggunakan operator bersyarat nol: someCollection.GetType().GetInterface(typeof(IEnumerable<>).Name)?.GetGenericArguments()?.FirstOrDefault()
Mass Dot Net
2

Gunakan saja typeof(T)

EDIT: Atau gunakan .GetType (). GetGenericParameter () pada objek yang dibuat jika Anda tidak memiliki T.

kendali
sumber
Anda tidak selalu memiliki T.
jason
Benar, dalam hal ini Anda dapat menggunakan .GetType (). Saya akan mengubah jawaban saya.
kendalikan
2

Alternatif untuk situasi yang lebih sederhana di mana itu akan menjadi IEnumerable<T>atau T- gunakan catatan GenericTypeArgumentsalih-alih GetGenericArguments().

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

    return genericType;
} else {
    return inputType;
}
Rob Church
sumber
1

Ini adalah perbaikan pada solusi Eli Algranti karena ia juga akan berfungsi di mana IEnumerable<>jenisnya berada di tingkat mana pun dalam pohon warisan.

Solusi ini akan mendapatkan tipe elemen dari apapun Type. Jika tipe bukan IEnumerable<>, itu akan mengembalikan tipe yang diteruskan. Untuk objek, gunakan GetType. Untuk jenis, gunakan typeof, lalu panggil metode ekstensi ini pada hasil.

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;
    }
}
Neo
sumber
1

Saya tahu ini agak lama, tetapi saya yakin metode ini akan mencakup semua masalah dan tantangan yang disebutkan di komentar. Penghargaan untuk Eli Algranti karena telah menginspirasi pekerjaan saya.

/// <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
sumber
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();
}

Ini adalah fungsi rekursif yang akan masuk ke dalam daftar tipe generik terlebih dahulu sampai mendapatkan definisi tipe konkret tanpa tipe generik dalam.

Saya menguji metode ini dengan tipe ini: ICollection<IEnumerable<ICollection<ICollection<IEnumerable<IList<ICollection<IEnumerable<IActionResult>>>>>>>>

yang harus dikembalikan IActionResult

Tyler Huskins
sumber
0

typeof(IEnumerable<Foo>). akan mengembalikan argumen umum pertama - dalam kasus ini .GetGenericArguments()[0]typeof(Foo)

Daniel Brückner
sumber
0

ini adalah cara saya biasanya melakukannya (melalui metode ekstensi):

public static Type GetIEnumerableUnderlyingType<T>(this T iEnumerable)
    {
        return typeof(T).GetTypeInfo().GetGenericArguments()[(typeof(T)).GetTypeInfo().GetGenericArguments().Length - 1];
    }
H7O
sumber
0

Ini versi ekspresi kueri Linq saya yang tidak dapat dibaca ..

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

Perhatikan bahwa metode ini juga memperhitungkan non-generik IEnumerable, ia mengembalikan objectdalam kasus ini, karena mengambil Typecontoh konkret daripada sebagai argumen. Ngomong-ngomong, karena x mewakili hal yang tidak diketahui , menurut saya video ini menarik , meskipun tidak relevan ..

Ken Kin
sumber