Bagaimana cara mendapatkan tipe T dari anggota kelas umum atau metode?

675

Katakanlah saya memiliki anggota generik di kelas atau metode, jadi:

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

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

Ketika saya instantiate kelas, Tmenjadi MyTypeObject1, sehingga kelas memiliki properti daftar generik: List<MyTypeObject1>. Hal yang sama berlaku untuk metode generik di kelas non-generik:

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

        // get type of T
    }
}

Saya ingin tahu, jenis objek apa yang terdapat dalam daftar kelas saya. Jadi daftar properti yang dipanggil Baratau variabel lokal baz, berisi jenis apa T?

Saya tidak bisa melakukannya Bar[0].GetType(), karena daftar mungkin berisi elemen nol. Bagaimana saya bisa melakukannya?

Patrick Desjardins
sumber

Jawaban:

695

Jika saya mengerti dengan benar, daftar Anda memiliki parameter tipe yang sama dengan kelas kontainer itu sendiri. Jika ini masalahnya, maka:

Type typeParameterType = typeof(T);

Jika Anda berada dalam situasi beruntung memiliki objectsebagai parameter tipe, lihat jawaban Marc .

Tamas Czinege
sumber
4
Lol - ya, sangat benar; Saya berasumsi bahwa OP hanya memiliki object, IList, atau serupa - tapi ini bisa sangat baik menjadi jawaban yang tepat.
Marc Gravell
32
Saya suka membaca typeof. Jika Anda ingin mengetahui tipe T, gunakan saja typeof(T):)
demoncodemonkey
2
Saya sebenarnya hanya menggunakan typeof (Type) dan itu berfungsi dengan baik.
Anton
1
Anda tidak dapat menggunakan typeof () dengan parameter generik.
Reynevan
2
@Reynevan Tentu saja Anda dapat menggunakan typeof()parameter generik. Apakah Anda punya contoh di mana itu tidak akan berhasil? Atau apakah Anda membingungkan parameter dan referensi tipe?
Luaan
520

(catatan: Saya berasumsi bahwa yang Anda tahu adalah objectatauIList atau serupa, dan bahwa daftar bisa menjadi semua jenis saat runtime)

Jika Anda tahu itu adalah List<T>, maka:

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

Pilihan lain adalah melihat pengindeks:

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

Menggunakan TypeInfo baru:

using System.Reflection;
// ...
var type = abc.GetType().GetTypeInfo().GenericTypeArguments[0];
Marc Gravell
sumber
1
Ketik type = abc.GetType (). GetGenericArguments () [0]; ==> Indeks array di luar batas ...
Patrick Desjardins
28
@Dok: maka itu bukan Daftar <T>
Marc Gravell
Butuh sesuatu untuk BindingList atau Daftar atau objek apa pun yang memiliki <T>. Apa yang saya lakukan menggunakan BindingListView kustom <T>
Patrick Desjardins
1
Cobalah dengan BindingList <T>, BindingListView <T> kami mewarisi dari BindingList <T> dan keduanya saya telah mencoba kedua opsi Anda dan tidak berfungsi. Saya mungkin melakukan sesuatu yang salah ... tapi saya pikir solusi ini berfungsi untuk tipe Daftar <T> tetapi tidak untuk tipe daftar lainnya.
Patrick Desjardins
Ketik type = abc.GetType (). GetProperty ("Item"). PropertyType; kembalikan BindingListView <MyObject> alih-alih MyObject ...
Patrick Desjardins
49

Dengan metode ekstensi berikut, Anda dapat keluar tanpa refleksi:

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

Atau lebih umum:

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

Pemakaian:

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
sumber
10
Ini hanya berguna jika Anda sudah tahu jenis Twaktu kompilasi. Dalam hal ini, Anda tidak perlu kode sama sekali.
rekursif
'mengembalikan default (T);'
fantastory
1
@recursive: Berguna jika Anda bekerja dengan daftar jenis anonim.
JJJ
Saya melakukan hal yang sama persis sebelum saya membaca jawaban Anda tetapi saya telah memanggil milik sayaItemType
toddmo
31

Mencoba

list.GetType().GetGenericArguments()
Rauhotz
sumber
7
Daftar baru <int> () .GetType (). GetGenericArguments () mengembalikan System.Type [1] di sini dengan System.Int32 sebagai entri
Rauhotz
@ Rauhotz GetGenericArgumentsmetode mengembalikan objek Array Type, yang Anda perlu kemudian menguraikan posisi Tipe Generik yang Anda butuhkan. Seperti Type<TKey, TValue>: Anda harus GetGenericArguments()[0]mendapatkan TKeytipe dan GetGenericArguments()[1]mendapatkan TValuetipe
GoldBishop
14

Itu bekerja untuk saya. Di mana myList adalah semacam daftar yang tidak diketahui.

IEnumerable myEnum = myList as IEnumerable;
Type entryType = myEnum.AsQueryable().ElementType;
Carlos Rodriguez
sumber
1
Saya mendapatkan kesalahan karena memerlukan argumen tipe (yaitu <T>)
Joseph Humfrey
Joseph dan yang lainnya, untuk menghilangkan kesalahan itu ada di System.Collections.
user2334883
1
Baris kedua dibutuhkan untukku. A Listsudah merupakan implementasi dari IEnumerable, jadi para pemain sepertinya tidak menambahkan apa-apa. Tapi terima kasih, ini solusi yang bagus.
pipedreambomb
10

Jika Anda tidak memerlukan variabel Type keseluruhan dan hanya ingin memeriksa tipe Anda dapat dengan mudah membuat variabel temp dan gunakan operator.

T checkType = default(T);

if (checkType is MyClass)
{}
Sebi
sumber
9

Anda dapat menggunakan yang ini untuk jenis kembali daftar umum:

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

Pertimbangkan ini: Saya menggunakannya untuk mengekspor 20 daftar yang diketik dengan cara yang sama:

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
sumber
1
Ini tidak berfungsi jika T adalah superclass abstrak dari objek yang ditambahkan sebenarnya. Belum lagi, hanya new T();akan melakukan hal yang sama (T)Activator.CreateInstance(typeof(T));. Memang mengharuskan Anda menambahkan where T : new()ke definisi kelas / fungsi, tetapi jika Anda ingin membuat objek, itu harus tetap dilakukan.
Nyerguds
Juga, Anda menelepon GetTypepada FirstOrDefaultentri mengakibatkan pengecualian referensi null potensial. Jika Anda yakin itu akan mengembalikan setidaknya satu item, mengapa tidak menggunakannya First?
Mathias Lykkegaard Lorenzen
6

Saya menggunakan metode ekstensi ini untuk mencapai sesuatu yang serupa:

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

Anda menggunakannya seperti ini:

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

Ini bukan yang Anda cari, tetapi sangat membantu dalam menunjukkan teknik yang terlibat.

Ken Smith
sumber
Hai, terima kasih atas jawaban Anda, dapatkah Anda menambahkan ekstensi untuk "StripStartingWith" juga?
Cedric Arnould
1
@CedricArnould - Ditambahkan.
Ken Smith
5

The GetGenericArgument()Metode harus diatur pada Basis Jenis Misalnya Anda (yang kelasnya adalah kelas generikmyClass<T> ). Jika tidak, ia mengembalikan suatu tipe [0]

Contoh:

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

Anda bisa mendapatkan jenis "T" dari semua jenis koleksi yang mengimplementasikan IEnumerable <T> dengan yang berikut:

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

Perhatikan bahwa itu tidak mendukung tipe pengumpulan yang mengimplementasikan IEnumerable <T> dua kali, misalnya

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

Saya kira Anda dapat mengembalikan berbagai jenis jika Anda perlu mendukung ini ...

Selain itu, Anda mungkin juga ingin men-cache hasil per jenis koleksi jika Anda sering melakukan ini (misalnya dalam satu lingkaran).

Dan Malcolm
sumber
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
sumber
1
Ini muncul untuk menjawab pertanyaan apakah jenisnya adalah jenis daftar-y, tetapi pertanyaannya lebih lanjut tentang bagaimana menentukan parameter tipe generik jenis yang dikenal sebagai Daftar yang sudah diinisialisasi.
Nathan Tuggy
1

Menggunakan solusi 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
sumber
0

Jika Anda ingin mengetahui jenis yang mendasari properti, coba ini:

propInfo.PropertyType.UnderlyingSystemType.GenericTypeArguments[0]
Fatih Çelik
sumber
0

Beginilah cara saya melakukannya

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

Lalu panggil seperti ini

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

ATAU

var item = Activator.CreateInstance(Bar.GetType().GetElementType());
Alen.Toma
sumber
-9

Tipe:

type = list.AsEnumerable().SingleOrDefault().GetType();
Ferenc Mucsi
sumber
1
Ini akan melempar NullReferenceException jika daftar tidak memiliki elemen di dalamnya untuk diuji.
rossisdead
1
SingleOrDefault()juga melempar InvalidOperationExceptionketika ada dua atau lebih elemen.
devgeezer
Jawaban ini salah, seperti yang ditunjukkan dengan benar oleh \ @rossisdead dan \ @devgeezer.
Oliver