Pertanyaan Bagaimana cara menggunakan refleksi untuk memanggil metode generik?


Apa cara terbaik untuk memanggil metode generik ketika parameter jenis tidak diketahui pada waktu kompilasi, tetapi sebaliknya diperoleh secara dinamis saat runtime?

Pertimbangkan kode contoh berikut - di dalam Example() metode, apa cara paling ringkas untuk memohon GenericMethod<T>() menggunakan Type disimpan dalam myType variabel?

public class Sample
{
    public void Example(string typeName)
    {
        Type myType = FindType(typeName);

        // What goes here to call GenericMethod<T>()?
        GenericMethod<myType>(); // This doesn't work

        // What changes to call StaticMethod<T>()?
        Sample.StaticMethod<myType>(); // This also doesn't work
    }

    public void GenericMethod<T>()
    {
        // ...
    }

    public static void StaticMethod<T>()
    {
        //...
    }
}

844
2017-10-24 05:17


asal


Jawaban:


Anda perlu menggunakan refleksi untuk mendapatkan metode untuk memulai dengan, lalu "membangun" dengan menyediakan argumen jenis dengan MakeGenericMethod:

MethodInfo method = typeof(Sample).GetMethod("GenericMethod");
MethodInfo generic = method.MakeGenericMethod(myType);
generic.Invoke(this, null);

Untuk metode statis, lewati null sebagai argumen pertama Invoke. Itu tidak ada hubungannya dengan metode generik - itu hanya refleksi normal.

Seperti disebutkan, banyak hal ini lebih sederhana pada penggunaan C # 4 dynamic - jika Anda bisa menggunakan inferensi jenis, tentu saja. Ini tidak membantu dalam kasus-kasus di mana inferensi tipe tidak tersedia, seperti contoh yang tepat dalam pertanyaan.


921
2017-10-24 06:13



Hanya tambahan untuk jawaban asli. Meskipun ini akan berfungsi:

MethodInfo method = typeof(Sample).GetMethod("GenericMethod");
MethodInfo generic = method.MakeGenericMethod(myType);
generic.Invoke(this, null);

Ini juga sedikit berbahaya karena Anda kehilangan waktu kompilasi GenericMethod. Jika Anda kemudian melakukan refactoring dan rename GenericMethod, kode ini tidak akan melihat dan akan gagal pada waktu proses. Juga, jika ada pasca-pemrosesan perakitan (misalnya mengaburkan atau menghapus metode / kelas yang tidak digunakan) kode ini mungkin rusak juga.

Jadi, jika Anda tahu metode yang Anda tautkan pada waktu kompilasi, dan ini tidak disebut jutaan kali sehingga overhead tidak masalah, saya akan mengubah kode ini menjadi:

Action<> GenMethod = GenericMethod<int>;  //change int by any base type 
                                          //accepted by GenericMethod
MethodInfo method = this.GetType().GetMethod(GenMethod.Method.Name);
MethodInfo generic = method.MakeGenericMethod(myType);
generic.Invoke(this, null);

Meskipun tidak terlalu cantik, Anda memiliki referensi waktu kompilasi GenericMethod di sini, dan jika Anda refactor, hapus atau lakukan apa pun dengan GenericMethod, kode ini akan tetap berfungsi, atau setidaknya putus pada waktu kompilasi (jika misalnya Anda menghapusnya GenericMethod).

Cara lain untuk melakukan hal yang sama adalah dengan membuat kelas pembungkus baru, dan membuatnya melalui Activator. Saya tidak tahu apakah ada cara yang lebih baik.


135
2018-02-27 16:11



Memanggil metode umum dengan parameter tipe yang hanya diketahui saat waktu proses dapat sangat disederhanakan dengan menggunakan a dynamic ketik bukan API refleksi.

Untuk menggunakan teknik ini, tipe harus diketahui dari objek yang sebenarnya (bukan hanya sebuah contoh dari Type kelas). Jika tidak, Anda harus membuat objek dari jenis itu atau menggunakan API refleksi standar larutan. Anda dapat membuat objek dengan menggunakan Activator.CreateInstance metode.

Jika Anda ingin memanggil metode generik, bahwa dalam penggunaan "normal" akan memiliki tipe disimpulkan, maka itu hanya datang untuk casting objek dari jenis yang tidak dikenal ke dynamic. Inilah contohnya:

class Alpha { }
class Beta { }
class Service
{
    public void Process<T>(T item)
    {
        Console.WriteLine("item.GetType(): " + item.GetType()
                          + "\ttypeof(T): " + typeof(T));
    }
}

class Program
{
    static void Main(string[] args)
    {
        var a = new Alpha();
        var b = new Beta();

        var service = new Service();
        service.Process(a); // Same as "service.Process<Alpha>(a)"
        service.Process(b); // Same as "service.Process<Beta>(b)"

        var objects = new object[] { a, b };
        foreach (var o in objects)
        {
            service.Process(o); // Same as "service.Process<object>(o)"
        }
        foreach (var o in objects)
        {
            dynamic dynObj = o;
            service.Process(dynObj); // Or write "service.Process((dynamic)o)"
        }
    }
}

Dan inilah output dari program ini:

item.GetType(): Alpha    typeof(T): Alpha
item.GetType(): Beta     typeof(T): Beta
item.GetType(): Alpha    typeof(T): System.Object
item.GetType(): Beta     typeof(T): System.Object
item.GetType(): Alpha    typeof(T): Alpha
item.GetType(): Beta     typeof(T): Beta

Process adalah metode instance generik yang menulis tipe nyata dari argumen yang dilewatkan (dengan menggunakan GetType() metode) dan jenis parameter generik (dengan menggunakan typeof operator).

Dengan melemparkan argumen objek ke dynamic jenis kami ditangguhkan dengan memberikan parameter jenis hingga runtime. Ketika Process metode dipanggil dengan dynamic argumen maka compiler tidak peduli tentang jenis argumen ini. Kompilator menghasilkan kode yang pada saat runtime memeriksa tipe-tipe nyata dari argumen yang dilewatkan (dengan menggunakan refleksi) dan memilih metode terbaik untuk dipanggil. Di sini hanya ada satu metode umum ini, jadi ini dipanggil dengan parameter tipe yang tepat.

Dalam contoh ini, hasilnya sama dengan jika Anda menulis:

foreach (var o in objects)
{
    MethodInfo method = typeof(Service).GetMethod("Process");
    MethodInfo generic = method.MakeGenericMethod(o.GetType());
    generic.Invoke(service, new object[] { o });
}

Versi dengan tipe dinamis pasti lebih pendek dan lebih mudah untuk ditulis. Anda juga tidak perlu khawatir tentang kinerja memanggil fungsi ini beberapa kali. Panggilan berikutnya dengan argumen dari tipe yang sama harus lebih cepat berkat caching mekanisme dalam DLR. Tentu saja, Anda dapat menulis kode yang dikirim cache cache, tetapi dengan menggunakan dynamic ketik Anda mendapatkan perilaku ini secara gratis.

Jika metode generik yang ingin Anda panggil tidak memiliki argumen tipe parametrized (sehingga parameter jenisnya tidak dapat disimpulkan), maka Anda dapat membungkus pemanggilan metode generik dalam metode pembantu seperti dalam contoh berikut:

class Program
{
    static void Main(string[] args)
    {
        object obj = new Alpha();

        Helper((dynamic)obj);
    }

    public static void Helper<T>(T obj)
    {
        GenericMethod<T>();
    }

    public static void GenericMethod<T>()
    {
        Console.WriteLine("GenericMethod<" + typeof(T) + ">");
    }
}

Peningkatan keamanan tipe

Apa yang benar-benar hebat dalam menggunakan dynamic objek sebagai pengganti untuk menggunakan refleksi API adalah bahwa Anda hanya kehilangan waktu kompilasi memeriksa jenis khusus ini yang Anda tidak tahu sampai runtime. Argumen lain dan nama metode dianalisis secara statis oleh compiler seperti biasa. Jika Anda menghapus atau menambahkan lebih banyak argumen, ubah jenisnya atau ganti nama nama metode, maka Anda akan mendapatkan kesalahan waktu kompilasi. Ini tidak akan terjadi jika Anda memberikan nama metode sebagai string Type.GetMethod dan argumen sebagai array objek MethodInfo.Invoke.

Di bawah ini adalah contoh sederhana yang menggambarkan bagaimana beberapa kesalahan dapat ditangkap pada waktu kompilasi (kode komentar) dan lainnya saat runtime. Ini juga menunjukkan bagaimana DLR mencoba untuk menyelesaikan metode mana yang harus dihubungi.

interface IItem { }
class FooItem : IItem { }
class BarItem : IItem { }
class Alpha { }

class Program
{
    static void Main(string[] args)
    {
        var objects = new object[] { new FooItem(), new BarItem(), new Alpha() };
        for (int i = 0; i < objects.Length; i++)
        {
            ProcessItem((dynamic)objects[i], "test" + i, i);

            //ProcesItm((dynamic)objects[i], "test" + i, i);
            //compiler error: The name 'ProcesItm' does not
            //exist in the current context

            //ProcessItem((dynamic)objects[i], "test" + i);
            //error: No overload for method 'ProcessItem' takes 2 arguments
        }
    }

    static string ProcessItem<T>(T item, string text, int number)
        where T : IItem
    {
        Console.WriteLine("Generic ProcessItem<{0}>, text {1}, number:{2}",
                          typeof(T), text, number);
        return "OK";
    }
    static void ProcessItem(BarItem item, string text, int number)
    {
        Console.WriteLine("ProcessItem with Bar, " + text + ", " + number);
    }
}

Di sini kita kembali menjalankan beberapa metode dengan melemparkan argumen ke dynamic mengetik. Hanya verifikasi tipe argumen pertama yang ditunda untuk waktu proses. Anda akan mendapatkan kesalahan kompiler jika nama metode yang Anda panggil tidak ada atau jika argumen lain tidak valid (jumlah argumen salah atau jenis salah).

Ketika Anda lulus dynamic argumen ke suatu metode maka panggilan ini adalah akhir-akhir ini terikat. Metode resolusi kelebihan beban terjadi saat runtime dan mencoba untuk memilih kelebihan beban terbaik. Jadi jika Anda memohon ProcessItem metode dengan objek BarItem ketik maka Anda akan benar-benar memanggil metode non-generik, karena ini adalah kecocokan yang lebih baik untuk jenis ini. Namun, Anda akan mendapatkan kesalahan runtime ketika Anda melewati argumen Alpha ketik karena tidak ada metode yang dapat menangani objek ini (metode generik memiliki kendala where T : IItem dan Alpha kelas tidak mengimplementasikan antarmuka ini). Tapi itulah intinya. Compiler tidak memiliki informasi bahwa panggilan ini valid. Anda sebagai programmer tahu ini, dan Anda harus memastikan bahwa kode ini berjalan tanpa kesalahan.

Return type gotcha

Ketika Anda memanggil metode non-void dengan parameter tipe dinamis, jenis kembalinya mungkin akan menjadi dynamic terlalu. Jadi, jika Anda mengubah contoh sebelumnya ke kode ini:

var result = ProcessItem((dynamic)testObjects[i], "test" + i, i);

maka jenis objek hasilnya akan menjadi dynamic. Ini karena compiler tidak selalu tahu metode mana yang akan dipanggil. Jika Anda tahu jenis kembalinya panggilan fungsi maka Anda harus secara implisit mengkonversi ke jenis yang diperlukan sehingga sisa kode diketik secara statis:

string result = ProcessItem((dynamic)testObjects[i], "test" + i, i);

Anda akan mendapatkan kesalahan runtime jika jenisnya tidak cocok.

Sebenarnya, jika Anda mencoba untuk mendapatkan nilai hasil dalam contoh sebelumnya maka Anda akan mendapatkan error runtime dalam iterasi loop kedua. Ini karena Anda mencoba menyimpan nilai kembalian dari fungsi kosong.


105
2018-03-16 19:21



Dengan C # 4.0, refleksi tidak diperlukan karena DLR dapat memanggilnya menggunakan jenis runtime. Karena menggunakan perpustakaan DLR adalah jenis rasa sakit dinamis (bukan kode kompiler C # menghasilkan untuk Anda), kerangka sumber terbuka Dynamitey (.net standard 1.5) memberi Anda akses run-time cache yang mudah ke panggilan yang sama yang akan dibuat oleh compiler untuk Anda.

var name = InvokeMemberName.Create;
Dynamic.InvokeMemberAction(this, name("GenericMethod", new[]{myType}));


var staticContext = InvokeContext.CreateStatic;
Dynamic.InvokeMemberAction(staticContext(typeof(Sample)), name("StaticMethod", new[]{myType}));

11
2017-07-05 13:40



Tambahkan ke Jawaban Adrian Gallero:

Memanggil metode generik dari info jenis melibatkan tiga langkah.

TLDR: Memanggil metode generik yang dikenal dengan objek tipe dapat diselesaikan dengan:

((Action)GenericMethod<object>)
    .Method
    .GetGenericMethodDefinition()
    .MakeGenericMethod(typeof(string))
    .Invoke(this, null);

dimana GenericMethod<object> adalah nama metode untuk memanggil dan jenis apa pun yang memenuhi batasan generik.

(Aksi) cocok dengan tanda tangan dari metode yang akan dipanggil yaitu (Func<string,string,int> atau Action<bool>)

Langkah 1 mendapatkan MethodInfo untuk definisi metode generik

Metode 1: Gunakan GetMethod () atau GetMethods () dengan jenis yang sesuai atau bendera yang mengikat.

MethodInfo method = typeof(Sample).GetMethod("GenericMethod");

Metode 2: Buat delegasi, dapatkan objek MethodInfo dan kemudian panggil GetGenericMethodDefinition

Dari dalam kelas yang berisi metode:

MethodInfo method = ((Action)GenericMethod<object>)
    .Method
    .GetGenericMethodDefinition();

MethodInfo method = ((Action)StaticMethod<object>)
    .Method
    .GetGenericMethodDefinition();

Dari luar kelas yang berisi metode:

MethodInfo method = ((Action)(new Sample())
    .GenericMethod<object>)
    .Method
    .GetGenericMethodDefinition();

MethodInfo method = ((Action)Sample.StaticMethod<object>)
    .Method
    .GetGenericMethodDefinition();

Dalam C #, nama metode, yaitu "ToString" atau "GenericMethod" sebenarnya merujuk pada sekelompok metode yang mungkin berisi satu atau lebih metode. Sampai Anda memberikan jenis parameter metode, tidak diketahui yang mana metode yang Anda maksud.

((Action)GenericMethod<object>) mengacu pada delegasi untuk metode tertentu. ((Func<string, int>)GenericMethod<object>) mengacu pada kelebihan yang berbeda dari GenericMethod

Metode 3: Buat ekspresi lambda yang berisi ekspresi pemanggilan metode, dapatkan objek MethodInfo, lalu GetGenericMethodDefinition

MethodInfo method = ((MethodCallExpression)((Expression<Action<Sample>>)(
    (Sample v) => v.GenericMethod<object>()
    )).Body).Method.GetGenericMethodDefinition();

Ini dipecah menjadi

Buat ekspresi lambda di mana tubuh adalah panggilan untuk metode yang Anda inginkan.

Expression<Action<Sample>> expr = (Sample v) => v.GenericMethod<object>();

Ekstrak badan dan transmisikan ke MethodCallExpression

MethodCallExpression methodCallExpr = (MethodCallExpression)expr.Body;

Dapatkan definisi metode generik dari metode ini

MethodInfo methodA = methodCallExpr.Method.GetGenericMethodDefinition();

Langkah 2 memanggil MakeGenericMethod untuk membuat metode umum dengan tipe yang sesuai.

MethodInfo generic = method.MakeGenericMethod(myType);

Langkah 3 adalah memohon metode dengan argumen yang tepat.

generic.Invoke(this, null);

5
2018-01-09 22:20



Tidak ada yang memberikan "Refleksi klasik"Solusi, jadi di sini adalah contoh kode lengkap:

using System;
using System.Collections;
using System.Collections.Generic;

namespace DictionaryRuntime
{
    public class DynamicDictionaryFactory
    {
        /// <summary>
        /// Factory to create dynamically a generic Dictionary.
        /// </summary>
        public IDictionary CreateDynamicGenericInstance(Type keyType, Type valueType)
        {
            //Creating the Dictionary.
            Type typeDict = typeof(Dictionary<,>);

            //Creating KeyValue Type for Dictionary.
            Type[] typeArgs = { keyType, valueType };

            //Passing the Type and create Dictionary Type.
            Type genericType = typeDict.MakeGenericType(typeArgs);

            //Creating Instance for Dictionary<K,T>.
            IDictionary d = Activator.CreateInstance(genericType) as IDictionary;

            return d;

        }
    }
}

Di atas DynamicDictionaryFactory kelas memiliki metode

CreateDynamicGenericInstance(Type keyType, Type valueType)

dan itu menciptakan dan mengembalikan turunan contoh, jenis kunci dan nilai siapa persisnya yang ditentukan pada panggilan keyType dan valueType.

Berikut ini contoh lengkapnya bagaimana memanggil metode ini untuk instantiate dan menggunakan Dictionary<String, int> :

using System;
using System.Collections.Generic;

namespace DynamicDictionary
{
    class Test
    {
        static void Main(string[] args)
        {
            var factory = new DictionaryRuntime.DynamicDictionaryFactory();
            var dict = factory.CreateDynamicGenericInstance(typeof(String), typeof(int));

            var typedDict = dict as Dictionary<String, int>;

            if (typedDict != null)
            {
                Console.WriteLine("Dictionary<String, int>");

                typedDict.Add("One", 1);
                typedDict.Add("Two", 2);
                typedDict.Add("Three", 3);

                foreach(var kvp in typedDict)
                {
                    Console.WriteLine("\"" + kvp.Key + "\": " + kvp.Value);
                }
            }
            else
                Console.WriteLine("null");
        }
    }
}

Ketika aplikasi konsol di atas dijalankan, kami mendapatkan hasil yang benar dan diharapkan:

Dictionary<String, int>
"One": 1
"Two": 2
"Three": 3

2
2017-08-24 01:24



Ini adalah 2 sen saya berdasarkan Jawaban Grax, tetapi dengan dua parameter yang diperlukan untuk metode umum.

Asumsikan metode Anda didefinisikan sebagai berikut dalam kelas Helpers:

public class Helpers
{
    public static U ConvertCsvDataToCollection<U, T>(string csvData)
    where U : ObservableCollection<T>
    {
      //transform code here
    }
}

Dalam kasus saya, tipe U selalu merupakan koleksi objek penyimpanan yang dapat diamati dari tipe T.

Ketika saya memiliki tipe yang telah ditentukan, saya pertama kali membuat objek "dummy" yang mewakili koleksi yang dapat diamati (U) dan objek yang tersimpan di dalamnya (T) dan yang akan digunakan di bawah ini untuk mendapatkan jenisnya ketika memanggil Make

object myCollection = Activator.CreateInstance(collectionType);
object myoObject = Activator.CreateInstance(objectType);

Kemudian panggil GetMethod untuk menemukan fungsi Umum Anda:

MethodInfo method = typeof(Helpers).
GetMethod("ConvertCsvDataToCollection");

Sejauh ini, panggilan di atas cukup mirip dengan apa yang dijelaskan di atas tetapi dengan perbedaan kecil ketika Anda perlu harus melewati beberapa parameter untuk itu.

Anda harus melewati array Type [] ke fungsi MakeGenericMethod yang berisi tipe "dummy" objects yang dibuat di atas:

MethodInfo generic = method.MakeGenericMethod(
new Type[] {
   myCollection.GetType(),
   myObject.GetType()
});

Setelah selesai, Anda perlu memanggil metode Invoke seperti yang disebutkan di atas.

generic.Invoke(null, new object[] { csvData });

Dan selesai. Bekerja pesona!

MEMPERBARUI:

Sebagai @Bevan disorot, saya tidak perlu membuat array ketika memanggil fungsi MakeGenericMethod seperti yang dibutuhkan dalam params dan saya tidak perlu membuat objek untuk mendapatkan jenis karena saya hanya bisa melewati jenis langsung ke fungsi ini. Dalam kasus saya, karena saya memiliki jenis yang telah ditentukan di kelas lain, saya cukup mengubah kode saya menjadi:

object myCollection = null;

MethodInfo method = typeof(Helpers).
GetMethod("ConvertCsvDataToCollection");

MethodInfo generic = method.MakeGenericMethod(
   myClassInfo.CollectionType,
   myClassInfo.ObjectType
);

myCollection = generic.Invoke(null, new object[] { csvData });

myClassInfo berisi 2 properti bertipe Type yang saya atur pada waktu proses berdasarkan nilai enum yang dilewatkan ke konstruktor dan akan memberi saya jenis yang relevan yang kemudian saya gunakan dalam MakeGenericMethod.

Terima kasih sekali lagi untuk menyoroti @Bevan ini.


0
2017-10-22 23:28