Bagaimana saya menggunakan refleksi untuk menjalankan metode pribadi?

326

Ada sekelompok metode pribadi di kelas saya, dan saya perlu memanggil satu secara dinamis berdasarkan nilai input. Baik kode pemanggilan dan metode target dalam contoh yang sama. Kode ini terlihat seperti ini:

MethodInfo dynMethod = this.GetType().GetMethod("Draw_" + itemType);
dynMethod.Invoke(this, new object[] { methodParams });

Dalam hal ini, GetMethod()tidak akan mengembalikan metode pribadi. Apa BindingFlagsyang saya butuhkan untuk memasok GetMethod()sehingga dapat menemukan metode pribadi?

Jeromy Irvine
sumber

Jawaban:

498

Cukup ubah kode Anda untuk menggunakan versiGetMethod overload yang menerima BindingFlags:

MethodInfo dynMethod = this.GetType().GetMethod("Draw_" + itemType, 
    BindingFlags.NonPublic | BindingFlags.Instance);
dynMethod.Invoke(this, new object[] { methodParams });

Inilah dokumentasi enumerasi BindingFlags .

wprl
sumber
248
Saya akan mendapatkan banyak masalah dengan ini.
Frank Schwieterman
1
BindingFlags.NonPublictidak mengembalikan privatemetode .. :(
Moumit
4
@ MoumitMondal adalah metode Anda statis? Anda harus menentukan BindingFlags.Instancejuga BindingFlags.NonPublicuntuk metode non-statis.
BrianS
Tidak ada @BrianS .. metodenya non-staticdan privatedan kelas diwarisi dari System.Web.UI.Page.. itu membuat saya bodoh juga .. saya tidak menemukan alasan .. :(
Moumit
3
Menambahkan BindingFlags.FlattenHierarchyakan memungkinkan Anda untuk mendapatkan metode dari kelas induk ke instance Anda.
Dragonthoughts
67

BindingFlags.NonPublictidak akan mengembalikan hasil apa pun dengan sendirinya. Ternyata, menggabungkannya dengan BindingFlags.Instancemelakukan trik.

MethodInfo dynMethod = this.GetType().GetMethod("Draw_" + itemType, 
    BindingFlags.NonPublic | BindingFlags.Instance);
Jeromy Irvine
sumber
Logika yang sama berlaku untuk internalfungsi juga
supertopi
Saya punya masalah serupa. Bagaimana jika "ini" adalah kelas anak dan Anda mencoba memanggil metode pribadi orang tua?
persianLife
Apakah mungkin untuk menggunakan ini untuk memanggil metode yang dilindungi kelas base.base?
Shiv
51

Dan jika Anda benar - benar ingin mendapatkan masalah, buat lebih mudah untuk dieksekusi dengan menulis metode ekstensi:

static class AccessExtensions
{
    public static object call(this object o, string methodName, params object[] args)
    {
        var mi = o.GetType ().GetMethod (methodName, System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance );
        if (mi != null) {
            return mi.Invoke (o, args);
        }
        return null;
    }
}

Dan penggunaan:

    class Counter
    {
        public int count { get; private set; }
        void incr(int value) { count += value; }
    }

    [Test]
    public void making_questionable_life_choices()
    {
        Counter c = new Counter ();
        c.call ("incr", 2);             // "incr" is private !
        c.call ("incr", 3);
        Assert.AreEqual (5, c.count);
    }
cod3monk3y
sumber
14
Berbahaya? Iya. Tapi ekstensi pembantu yang bagus ketika dibungkus dalam unit namespace pengujian saya. Terima kasih untuk ini.
Robert Wahler
5
Jika Anda peduli tentang pengecualian nyata yang dilemparkan dari metode yang disebut itu adalah ide yang baik untuk membungkusnya menjadi blok catch mencoba dan melemparkan kembali pengecualian batin sebagai gantinya ketika TargetInvokationException tertangkap. Saya melakukannya di ekstensi pembantu pengujian unit saya.
Slobodan Savkovic
2
Refleksi berbahaya? Hmmm ... C #, Jawa, Python ... sebenarnya semuanya berbahaya, bahkan dunia: D Anda hanya perlu berhati-hati tentang cara melakukannya dengan aman ...
Legenda
16

Microsoft baru-baru ini memodifikasi API refleksi yang membuat sebagian besar jawaban ini usang. Yang berikut ini harus bekerja pada platform modern (termasuk Xamarin.Forms dan UWP):

obj.GetType().GetTypeInfo().GetDeclaredMethod("MethodName").Invoke(obj, yourArgsHere);

Atau sebagai metode ekstensi:

public static object InvokeMethod<T>(this T obj, string methodName, params object[] args)
{
    var type = typeof(T);
    var method = type.GetTypeInfo().GetDeclaredMethod(methodName);
    return method.Invoke(obj, args);
}

catatan:

  • Jika metode yang diinginkan dalam superclass dari objyang Tgenerik harus secara eksplisit diatur dengan jenis superclass.

  • Jika metode ini asinkron, Anda dapat menggunakan await (Task) obj.InvokeMethod(…).

Owen James
sumber
Ini tidak berfungsi untuk versi .net UWP setidaknya karena hanya berfungsi untuk metode publik : " Mengembalikan koleksi yang berisi semua metode publik yang dideklarasikan pada tipe saat ini yang cocok dengan nama yang ditentukan ".
Dmytro Bondarenko
1
@ DmytroBondarenko Saya mengujinya terhadap metode pribadi dan itu berhasil. Saya memang melihat itu. Tidak yakin mengapa itu berperilaku berbeda dari dokumentasi, tetapi setidaknya itu berfungsi.
Owen James
Ya, saya tidak akan menyebut semua jawaban lain sudah usang, jika dokumentasi mengatakan GetDeclareMethod()dimaksudkan untuk digunakan hanya untuk mengambil metode publik.
Mass Dot Net
10

Apakah Anda benar-benar yakin ini tidak dapat dilakukan melalui warisan? Refleksi adalah hal terakhir yang harus Anda perhatikan ketika menyelesaikan masalah, itu membuat refactoring, memahami kode Anda, dan setiap analisis otomatis lebih sulit.

Sepertinya Anda seharusnya hanya memiliki kelas DrawItem1, DrawItem2, dll yang menggantikan dynMethod Anda.

Bill K.
sumber
1
@ Bill K: Mengingat keadaan lain, kami memutuskan untuk tidak menggunakan warisan untuk ini, karenanya penggunaan refleksi. Untuk sebagian besar kasus, kami akan melakukannya dengan cara itu.
Jeromy Irvine
8

Refleksi terutama pada anggota pribadi adalah salah

  • Refleksi merusak keamanan jenis. Anda dapat mencoba memanggil metode yang tidak ada (lagi), atau dengan parameter yang salah, atau dengan terlalu banyak parameter, atau tidak cukup ... atau bahkan dalam urutan yang salah (yang ini favorit saya :)). Omong-omong tipe pengembalian bisa berubah juga.
  • Refleksi lambat.

Refleksi anggota pribadi melanggar prinsip enkapsulasi dan dengan demikian mengekspos kode Anda sebagai berikut:

  • Tingkatkan kompleksitas kode Anda karena harus menangani perilaku batin kelas. Apa yang disembunyikan harus tetap disembunyikan.
  • Membuat kode Anda mudah rusak karena akan dikompilasi tetapi tidak akan berjalan jika metode ini mengubah namanya.
  • Membuat kode pribadi mudah dipecah karena jika bersifat pribadi itu tidak dimaksudkan untuk dipanggil dengan cara itu. Mungkin metode pribadi mengharapkan keadaan batin sebelum dipanggil.

Bagaimana jika saya tetap harus melakukannya?

Ada beberapa kasus, ketika Anda bergantung pada pihak ketiga atau Anda perlu beberapa api tidak terbuka, Anda harus melakukan beberapa refleksi. Beberapa juga menggunakannya untuk menguji beberapa kelas yang mereka miliki tetapi mereka tidak ingin mengubah antarmuka untuk memberikan akses ke anggota dalam hanya untuk tes.

Jika Anda melakukannya, lakukan dengan benar

  • Mengurangi mudah pecah:

Untuk memitigasi masalah yang mudah pecah, yang terbaik adalah mendeteksi potensi jeda dengan menguji dalam unit test yang akan berjalan dalam bentuk integrasi berkelanjutan atau semacamnya. Tentu saja, itu berarti Anda selalu menggunakan majelis yang sama (yang berisi anggota pribadi). Jika Anda menggunakan beban dinamis dan refleksi, Anda suka bermain dengan api, tetapi Anda selalu dapat menangkap Pengecualian bahwa panggilan dapat menghasilkan.

  • Mengurangi kelambatan refleksi:

Dalam versi terbaru dari .Net Framework, CreateDelegate dikalahkan oleh faktor 50 yang dipanggil MethodInfo:

// The following should be done once since this does some reflection
var method = this.GetType().GetMethod("Draw_" + itemType, 
  BindingFlags.NonPublic | BindingFlags.Instance);

// Here we create a Func that targets the instance of type which has the 
// Draw_ItemType method
var draw = (Func<TInput, Output[]>)_method.CreateDelegate(
                 typeof(Func<TInput, TOutput[]>), this);

drawpanggilan akan menjadi sekitar 50x lebih cepat daripada MethodInfo.Invoke digunakan drawsebagai standar Funcseperti itu:

var res = draw(methodParams);

Periksa pos saya ini untuk melihat patokan pada pemanggilan metode yang berbeda

Hebat
sumber
1
Meskipun saya mendapatkan injeksi ketergantungan harus menjadi cara yang lebih disukai untuk pengujian unit, saya kira digunakan dengan hati-hati tidak sepenuhnya jahat untuk menggunakan refleksi untuk mengakses unit yang seharusnya tidak dapat diakses untuk pengujian. Secara pribadi saya pikir juga pengubah [publik] [dilindungi] [pribadi] normal kita juga harus memiliki pengubah [Uji] [Komposisi] sehingga dimungkinkan untuk membuat hal-hal tertentu terlihat selama tahap-tahap ini tanpa dipaksa untuk membuat semuanya menjadi publik ( dan karena itu harus sepenuhnya mendokumentasikan metode-metode tersebut)
andrew pate
1
Terima kasih Fab karena telah mendaftarkan masalah merefleksikan anggota pribadi, itu telah menyebabkan saya meninjau kembali bagaimana perasaan saya tentang menggunakannya dan menghasilkan kesimpulan ... Menggunakan refleksi untuk menguji unit anggota pribadi Anda salah, namun membiarkan jalur kode tidak teruji benar-benar salah.
andrew pate
2
Tetapi ketika datang ke unit testing kode warisan umumnya tidak unit-testable, itu adalah cara yang brilian untuk melakukannya
TS
2

Tidak bisakah Anda hanya memiliki metode Draw yang berbeda untuk setiap jenis yang ingin Anda Draw? Kemudian panggil metode Draw yang kelebihan beban lewat objek tipe itemType yang akan ditarik.

Pertanyaan Anda tidak memperjelas apakah itemType benar-benar merujuk ke objek dari tipe yang berbeda.

Peter Hession
sumber
1

Saya pikir Anda dapat lulus di BindingFlags.NonPublicmana itu adalah GetMethodmetode.

Armin Ronacher
sumber
1

Meminta metode apa pun meskipun tingkat perlindungannya pada objek instance. Nikmati!

public static object InvokeMethod(object obj, string methodName, params object[] methodParams)
{
    var methodParamTypes = methodParams?.Select(p => p.GetType()).ToArray() ?? new Type[] { };
    var bindingFlags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static;
    MethodInfo method = null;
    var type = obj.GetType();
    while (method == null && type != null)
    {
        method = type.GetMethod(methodName, bindingFlags, Type.DefaultBinder, methodParamTypes, null);
        type = type.BaseType;
    }

    return method?.Invoke(obj, methodParams);
}
Maksim Shamihulau
sumber
0

Baca jawaban (tambahan) ini (yang terkadang merupakan jawaban) untuk memahami ke mana arah ini dan mengapa beberapa orang di utas ini mengeluh bahwa "masih belum berfungsi"

Saya menulis kode yang persis sama dengan salah satu jawaban di sini . Tapi saya masih punya masalah. Saya menempatkan break point

var mi = o.GetType().GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance );

Itu dieksekusi tetapi mi == null

Dan itu melanjutkan perilaku seperti ini sampai saya "membangun kembali" pada semua proyek yang terlibat. Saya menguji unit satu majelis sedangkan metode refleksi duduk di majelis ketiga. Itu benar-benar membingungkan tetapi saya menggunakan Window Segera untuk menemukan metode dan saya menemukan bahwa metode pribadi saya mencoba unit test memiliki nama lama (saya menamainya). Ini memberi tahu saya bahwa perakitan lama atau PDB masih ada di sana bahkan jika proyek unit test dibangun - untuk beberapa alasan proyek itu tes tidak dibangun. "membangun kembali" bekerja

TS
sumber
-1

BindingFlags.NonPublic

Khoth
sumber