Dapatkah saya mengubah bidang hanya baca pribadi di C # menggunakan refleksi?

115

Saya bertanya-tanya, karena banyak hal dapat dilakukan dengan menggunakan refleksi, dapatkah saya mengubah bidang hanya baca pribadi setelah konstruktor menyelesaikan eksekusinya?
(catatan: hanya rasa ingin tahu)

public class Foo
{
 private readonly int bar;

 public Foo(int num)
 {
  bar = num;
 }

 public int GetBar()
 {
  return bar;
 }
}

Foo foo = new Foo(123);
Console.WriteLine(foo.GetBar()); // display 123
// reflection code here...
Console.WriteLine(foo.GetBar()); // display 456
Ron Klein
sumber

Jawaban:

151

Kamu bisa:

typeof(Foo)
   .GetField("bar",BindingFlags.Instance|BindingFlags.NonPublic)
   .SetValue(foo,567);
Philippe Leybaert
sumber
2
Anda memang benar, tentu saja. Permintaan maaf saya. Dan ya, saya memang mencoba, tetapi saya mencoba untuk menyetel properti hanya-baca secara langsung, tidak menggunakan bidang dukungan. Apa yang saya coba tidak masuk akal. Solusi Anda bekerja dengan sangat baik (diuji lagi, kali ini dengan benar)
Sage Pourpre
bagaimana kita bisa melakukan ini dengan moq?
l --''''''--------- '' '' '' '' '' ''
Dalam dotnet core 3.0 ini tidak lagi memungkinkan. System.FieldAccessException muncul dan mengatakan: "Tidak dapat menyetel 'bar' bidang statis saat ini setelah jenis 'Foo' diinisialisasi."
David Perfors
54

Hal yang jelas adalah mencobanya:

using System;
using System.Reflection;

public class Test
{
    private readonly string foo = "Foo";

    public static void Main()
    {
        Test test = new Test();
        FieldInfo field = typeof(Test).GetField
            ("foo", BindingFlags.Instance | BindingFlags.NonPublic);
        field.SetValue(test, "Hello");
        Console.WriteLine(test.foo);
    }        
}

Ini bekerja dengan baik. (Java memiliki aturan yang berbeda, yang menarik - Anda harus secara eksplisit mengatur Fieldagar dapat diakses, dan itu hanya akan berfungsi untuk bidang contoh.)

Jon Skeet
sumber
4
Ahmed - tapi kami tidak menggunakan bahasa untuk melakukannya, jadi spesifikasi bahasa tidak mendapatkan suara ...
Marc Gravell
4
Yup - ada banyak hal yang bisa dilakukan yang "merusak" apa yang diinginkan bahasa tersebut. Anda dapat menjalankan penginisialisasi tipe beberapa kali, misalnya.
Jon Skeet
28
Saya perhatikan juga bahwa hanya karena Anda bisa dalam beberapa implementasi hari ini tidak berarti Anda bisa pada setiap implementasi sepanjang waktu. Saya tidak mengetahui tempat mana pun di mana kami mendokumentasikan bahwa bidang hanya-baca harus dapat berubah melalui refleksi. Sejauh yang saya tahu, implementasi CLI yang sesuai benar-benar bebas untuk mengimplementasikan bidang hanya-baca sehingga mereka membuang pengecualian ketika dimutasi melalui refleksi setelah konstruktor selesai.
Eric Lippert
3
Itu tidak bagus, karena ada kasus ketika saya perlu memperluas kelas lebih dari yang dirancang semula. Harus selalu ada cara untuk mengganti enkapsulasi jika direncanakan dengan baik. Satu-satunya alternatif adalah berdarah dan terkadang tidak mungkin tanpa menulis ulang bagian dari kerangka kerja.
drifter
5
@drifter: Pada saat itu, Anda membuka diri terhadap dunia yang penuh penderitaan. Anda mengandalkan detail implementasi saat ini yang dapat dengan mudah diubah di versi mendatang.
Jon Skeet
11

Saya setuju dengan jawaban lain yang bekerja secara umum dan terutama dengan komentar oleh E. Lippert bahwa ini bukan perilaku yang terdokumentasi dan karena itu bukan kode bukti masa depan.

Namun, kami juga melihat masalah lain. Jika Anda menjalankan kode Anda di lingkungan dengan izin terbatas Anda mungkin mendapatkan pengecualian.

Kami baru saja mengalami kasus di mana kode kami berfungsi dengan baik di mesin kami, tetapi kami menerima VerificationExceptionketika kode berjalan di lingkungan yang dibatasi. Pelakunya adalah panggilan refleksi ke penyetel bidang hanya baca. Ini berfungsi saat kami menghapus batasan hanya baca di bidang itu.

Andreas
sumber
2
Akan tertarik untuk mengetahui lingkungan mana yang menampilkan VerificationException
Sergey Zhukov
4

Anda bertanya mengapa Anda ingin memecahkan enkapsulasi seperti itu.

Saya menggunakan kelas pembantu entitas untuk menghidrasi entitas. Ini menggunakan refleksi untuk mendapatkan semua properti dari entitas kosong baru, dan mencocokkan nama properti / bidang dengan kolom di kumpulan hasil, dan menyetelnya menggunakan propertyinfo.setvalue ().

Saya tidak ingin orang lain dapat mengubah nilainya, tetapi saya juga tidak ingin melakukan semua upaya untuk metode hidrasi kode kustom untuk setiap entitas.

Banyak dari procs saya yang tersimpan mengembalikan hasil yang tidak berhubungan langsung dengan tabel atau tampilan, jadi gen kode ORM tidak melakukan apa-apa untuk saya.

Necroposter
sumber
1
Saya juga menggunakannya untuk melewati beberapa batasan api di mana nilainya di-hardcode, atau membutuhkan file konfigurasi yang tidak dapat saya berikan. (Ukuran file WSE 2.0 untuk lampiran DIME saat rakitan dimuat melalui refleksi, misalnya)
StingyJack
3

Cara sederhana lain untuk melakukan ini menggunakan unsafe (atau Anda dapat meneruskan bidang ke metode C melalui DLLImport dan mengaturnya di sana).

using System;

namespace TestReadOnly
{
    class Program
    {
        private readonly int i;

        public Program()
        {
            i = 66;
        }

        private unsafe void ForceSet()
        {
            fixed (int* ptr = &i) *ptr = 123;
        }

        static void Main(string[] args)
        {
            var program = new Program();
            Console.WriteLine("Contructed Value: " + program.i);
            program.ForceSet();
            Console.WriteLine("Forced Value: " + program.i);
        }
    }
}
zezba9000
sumber
2

Jawabannya ya, tapi yang lebih penting:

Mengapa Anda ingin? Membobol enkapsulasi secara sengaja sepertinya ide yang sangat buruk bagi saya.

Menggunakan refleksi untuk mengubah bidang baca saja atau konstan seperti menggabungkan Hukum Konsekuensi yang Tidak Diinginkan dengan Hukum Murphy .

Powerlord
sumber
1
jawabannya adalah "sekedar penasaran", seperti yang disebutkan di atas.
Ron Klein
Ada kalanya saya harus melakukan trik ini untuk menulis kode terbaik yang saya bisa. Contoh kasus - elegantcode.com/2008/04/17/testing-a-membership-provider
sparker
3
Saya juga menggunakan trik ini dalam proyek pengujian unit untuk mengganti nilai default yang tidak boleh diubah dalam kode bisnis apa pun ...
Koen
Dan saya mencoba untuk mengatur properti internal pribadi di perpustakaan kelas dasar untuk tujuan pengujian, khususnya API Keanggotaan, di mana MS menandai semuanya pribadi, internal dan tanpa penyetel pada properti. Ada beberapa kasus untuk ini, tetapi Anda benar jika pertanyaan diterapkan ke API di bawah kendali Anda
Chad Grant
3
Ada situasi di mana itu masuk akal. Pemeta O / R seperti NHibernate melakukannya sepanjang waktu untuk hidrasi, karena ini adalah satu-satunya cara Anda dapat mengimplementasikan enkapsulasi data untuk entitas persisten di tempat pertama.
chris
2

Jangan lakukan ini.

Saya baru saja menghabiskan satu hari untuk memperbaiki bug surealis di mana objek tidak dapat dideklarasikan jenisnya sendiri.

Memodifikasi bidang hanya baca berhasil satu kali. Tetapi jika Anda mencoba memodifikasinya lagi, Anda akan mendapatkan situasi seperti ini:

SoundDef mySound = Reflection_Modified_Readonly_SoundDef_Field;
if( !(mySound is SoundDef) )
    Log("Welcome to impossible-land!"); //This would run

Jadi jangan lakukan itu.

Ini ada di runtime Mono (mesin game Unity).

Tynan Sylvester
sumber
2
FYI - Unity Engine tidak dapat digunakan untuk menjawab pertanyaan khusus bahasa C # secara efektif seperti ini karena Unity melakukan kompilasi C # sendiri seolah-olah .cs adalah skrip, dalam arti tertentu. Saya tidak mengatakan maksud Anda tidak valid, tetapi jelas spesifik untuk Unity Engine & C # bersama-sama.
Dave Jellison
0

Saya hanya ingin menambahkan bahwa jika Anda perlu melakukan hal ini untuk pengujian unit, maka Anda dapat menggunakan:

A) PrivateObject kelas

B) Anda masih membutuhkan instance PrivateObject, tetapi Anda dapat membuat objek "Accessor" dengan Visual Studio. Cara: Meregenerasi Aksesor Pribadi

Jika Anda menyetel bidang pribadi suatu objek dalam kode Anda di luar pengujian unit, itu akan menjadi contoh "bau kode" Saya pikir mungkin satu-satunya alasan lain Anda ingin melakukan ini adalah jika Anda berurusan dengan pihak ketiga perpustakaan dan Anda tidak dapat mengubah kode kelas target. Meskipun begitu, Anda mungkin ingin menghubungi pihak ketiga, menjelaskan situasi Anda dan melihat apakah mereka tidak mau melanjutkan dan mengubah kode mereka untuk mengakomodasi kebutuhan Anda.

Dudeman3000
sumber