Czy mogę zmienić prywatne pole tylko do odczytu w C # przy użyciu odbicia?

115

Zastanawiam się, skoro wiele rzeczy można zrobić za pomocą odbicia, czy mogę zmienić prywatne pole tylko do odczytu po zakończeniu wykonywania przez konstruktora?
(uwaga: tylko ciekawość)

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
źródło

Odpowiedzi:

151

Możesz:

typeof(Foo)
   .GetField("bar",BindingFlags.Instance|BindingFlags.NonPublic)
   .SetValue(foo,567);
Philippe Leybaert
źródło
2
Oczywiście masz absolutną rację. Przepraszam. I tak, próbowałem, ale próbowałem ustawić właściwość tylko do odczytu bezpośrednio, nie używając pola zapasowego. To, czego próbowałem, nie miało sensu. Twoje rozwiązanie działa doskonale (ponownie przetestowane, tym razem poprawnie)
Sage Pourpre,
jak możemy to zrobić z moq?
l --''''''--------- '' '' '' '' '' '' '
W dotnet core 3.0 nie jest to już możliwe. System.FieldAccessException jest generowany z komunikatem: „Nie można ustawić tylko pola statycznego 'bar' początkowo po zainicjowaniu typu 'Foo'."
David Perfors
54

Oczywiste jest, aby spróbować:

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

To działa dobrze. (Java ma inne reguły, co ciekawe - musisz jawnie ustawić, Fieldaby był dostępny, a i tak będzie działać tylko dla pól instancji.)

Jon Skeet
źródło
4
Ahmed - ale nie używamy do tego języka, więc specyfikacja językowa nie podlega głosowaniu ...
Marc Gravell
4
Tak - wiele można zrobić, co „zepsuje” to, czego chce język. Na przykład inicjatory typu można uruchamiać wiele razy.
Jon Skeet
28
Zwracam również uwagę, że to, że możesz w niektórych implementacjach dzisiaj, nie oznacza, że ​​możesz je zawsze wykonywać w każdej implementacji. Nie znam żadnego miejsca, w którym dokumentujemy, że pola tylko do odczytu muszą być zmienne poprzez odbicie. O ile mi wiadomo, zgodna implementacja interfejsu CLI jest całkowicie darmowa i może zaimplementować pola tylko do odczytu, tak że rzucają wyjątki, gdy są mutowane przez odbicie po zakończeniu konstruktora.
Eric Lippert
3
Nie jest to jednak dobre, ponieważ zdarzają się przypadki, w których potrzebuję rozszerzyć klasę bardziej niż pierwotnie planowano. Zawsze powinien istnieć sposób na obejście enkapsulacji, gdy jest ona dobrze zaplanowana. Jedyne alternatywy są krwawe i czasami nie są możliwe bez przepisania części struktury.
włóczęga
5
@drifter: W tym momencie otwierasz się na świat bólu. Opierasz się na aktualnych szczegółach implementacji, które można łatwo zmienić w przyszłej wersji.
Jon Skeet
11

Zgadzam się z innymi odpowiedziami, ponieważ działa to ogólnie, a zwłaszcza z komentarzem E. Lipperta, że ​​nie jest to udokumentowane zachowanie, a zatem nie jest kodem przyszłościowym.

Jednak zauważyliśmy również inny problem. Jeśli uruchamiasz kod w środowisku z ograniczonymi uprawnieniami, możesz otrzymać wyjątek.

Właśnie mieliśmy przypadek, w którym nasz kod działał dobrze na naszych maszynach, ale otrzymaliśmy, VerificationExceptiongdy kod działał w ograniczonym środowisku. Winowajcą było wezwanie do refleksji skierowane do ustawiacza pola tylko do odczytu. Zadziałało, gdy usunęliśmy ograniczenie tylko do odczytu tego pola.

Andreas
źródło
2
Chciałbym wiedzieć, które środowiska generują wyjątek VerificationException
Sergey Zhukov
4

Zapytałeś, dlaczego chciałbyś przełamać hermetyzację w ten sposób.

Używam klasy pomocniczej jednostki do nawadniania jednostek. To używa odbicia, aby uzyskać wszystkie właściwości nowej pustej jednostki i dopasowuje nazwę właściwości / pola do kolumny w zestawie wyników i ustawia ją za pomocą propertyinfo.setvalue ().

Nie chcę, aby ktokolwiek inny mógł zmieniać wartość, ale nie chcę też podejmować wszelkich starań, aby dostosować metody hydratacji kodu dla każdej jednostki.

Wiele moich przechowywanych procesów zwraca zestawy wyników, które nie odpowiadają bezpośrednio tabelom lub widokom, więc kod ORM genów kodu nic dla mnie nie robi.

Necroposter
źródło
1
Użyłem go również, aby ominąć niektóre ograniczenia API, w których wartość została zakodowana na stałe lub wymagała pliku konfiguracyjnego, którego nie byłbym w stanie dostarczyć. (Rozmiar pliku WSE 2.0 dla załączników DIME, gdy zestawy były ładowane na przykład przez odbicie)
StingyJack
3

Innym prostym sposobem na zrobienie tego przy użyciu niebezpiecznego (lub możesz przekazać pole do metody C za pośrednictwem DLLImport i ustawić je tam).

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
źródło
2

Odpowiedź brzmi: tak, ale co ważniejsze:

Dlaczego miałbyś chcieć? Celowe zerwanie hermetyzacji wydaje mi się przerażająco złym pomysłem.

Używanie refleksji do zmiany pola tylko do odczytu lub stałego pola jest jak łączenie prawa niezamierzonych konsekwencji z prawem Murphy'ego .

Władca
źródło
1
odpowiedź brzmi „po prostu ciekawość”, jak wspomniano powyżej.
Ron Klein
Są chwile, kiedy muszę zrobić tylko tę sztuczkę, aby napisać najlepszy kod, jaki potrafię. Sprawa w punkcie - elegantcode.com/2008/04/17/testing-a-membership-provider
iskrownik
3
Ja również używam tej sztuczki w projektach testów jednostkowych, aby zastąpić wartości domyślne, które nie powinny być zmieniane w żadnym kodzie biznesowym ...
Koen
Próbowałem ustawić prywatne wewnętrzne właściwości w bibliotekach klas bazowych do celów testowych, a konkretnie w Membership API, gdzie MS oznaczył wszystko jako prywatne, wewnętrzne i bez ustawiania właściwości. Są na to przypadki, ale masz rację, jeśli pytanie dotyczyło interfejsu API znajdującego się pod Twoją kontrolą
Chad Grant
3
Są sytuacje, w których ma to sens. Mapery O / R, takie jak NHibernate, robią to przez cały czas w celu nawodnienia, ponieważ jest to jedyny sposób, w jaki można wdrożyć enkapsulację danych dla trwałych jednostek.
chris,
2

Nie rób tego.

Właśnie spędziłem dzień na naprawianiu surrealistycznego błędu, w którym obiekty nie mogły być własnego zadeklarowanego typu.

Modyfikacja pola tylko do odczytu zadziałała raz. Ale jeśli spróbujesz go ponownie zmodyfikować, otrzymasz takie sytuacje:

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

Więc nie rób tego.

Było to w środowisku wykonawczym Mono (silnik gry Unity).

Tynan Sylvester
źródło
2
FYI - Unity Engine nie może być używany do efektywnego odpowiadania na głębokie pytania specyficzne dla języka C #, takie jak to, ponieważ Unity wykonuje własną kompilację C # tak, jakby .cs były w pewnym sensie skryptami. Nie mówię, że twój punkt jest nieważny, ale z pewnością jest specyficzny dla silnika Unity i C # razem.
Dave Jellison,
0

Chcę tylko dodać, że jeśli potrzebujesz zrobić te rzeczy do testów jednostkowych, możesz użyć:

A) Klasa PrivateObject

B) Nadal będziesz potrzebować instancji PrivateObject, ale możesz generować obiekty „Accessor” za pomocą programu Visual Studio. Instrukcje: regenerowanie prywatnych akcesoriów

Jeśli ustawiasz prywatne pola obiektu w swoim kodzie poza testami jednostkowymi, byłby to przykład „zapachu kodu” Myślę, że być może jedynym innym powodem, dla którego chciałbyś to zrobić, jest to, że masz do czynienia z osobą trzecią i nie możesz zmienić kodu klasy docelowej. Nawet wtedy prawdopodobnie zechcesz skontaktować się z firmą zewnętrzną, wyjaśnić swoją sytuację i sprawdzić, czy nie zrobią tego i nie zmienią swojego kodu, aby spełnić Twoje potrzeby.

Dudeman3000
źródło