Kann ich ein privates schreibgeschütztes Feld in C # mithilfe von Reflektion ändern?

115

Ich frage mich, ob ich ein privates schreibgeschütztes Feld ändern kann, nachdem der Konstruktor seine Ausführung abgeschlossen hat, da viele Dinge mithilfe von Reflektion erledigt werden können.
(Anmerkung: nur Neugier)

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
quelle

Antworten:

151

Sie können:

typeof(Foo)
   .GetField("bar",BindingFlags.Instance|BindingFlags.NonPublic)
   .SetValue(foo,567);
Philippe Leybaert
quelle
2
Sie haben natürlich absolut Recht. Entschuldigen Sie. Und ja, ich habe es versucht, aber ich habe versucht, eine schreibgeschützte Eigenschaft direkt festzulegen, ohne ein Hintergrundfeld zu verwenden. Was ich versuchte, ergab keinen Sinn. Ihre Lösung funktioniert einwandfrei (erneut getestet, diesmal korrekt)
Sage Pourpre
Wie können wir das mit Moq machen?
l - '' '' '
In Dotnet Core 3.0 ist dies nicht mehr möglich. Es wird eine System.FieldAccessException ausgelöst, die besagt: "Das initiale statische Feld 'bar' kann nicht gesetzt werden, nachdem der Typ 'Foo' initialisiert wurde."
David Perfors
54

Das Offensichtliche ist, es zu versuchen:

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

Das funktioniert gut. (Interessanterweise hat Java andere Regeln - Sie müssen explizit festlegen Field, dass auf diese zugegriffen werden soll, und es funktioniert sowieso nur für Instanzfelder.)

Jon Skeet
quelle
4
Ahmed - aber wir verwenden nicht die Sprache, damit die Sprachspezifikation keine Stimme bekommt ...
Marc Gravell
4
Ja - es kann viel getan werden, was "bricht", was die Sprache will. Sie können beispielsweise Typinitialisierer mehrmals ausführen.
Jon Skeet
28
Ich stelle auch fest, dass nur weil Sie es heute in einer Implementierung können, dies nicht bedeutet, dass Sie es bei jeder Implementierung für alle Zeiten können. Mir ist kein Ort bekannt, an dem wir dokumentieren, dass schreibgeschützte Felder durch Reflexion veränderbar sein müssen. Soweit ich weiß, ist eine konforme Implementierung der CLI vollkommen frei, schreibgeschützte Felder so zu implementieren, dass sie Ausnahmen auslösen, wenn sie nach Abschluss des Konstruktors durch Reflektion mutiert werden.
Eric Lippert
3
Das ist jedoch nicht gut, da es Fälle gibt, in denen ich eine Klasse mehr erweitern muss, als sie ursprünglich vorgesehen war. Es sollte immer eine Möglichkeit geben, die Kapselung zu überschreiben, wenn sie gut geplant ist. Die einzigen Alternativen sind blutig und manchmal nicht möglich, ohne einen Teil des Frameworks neu zu schreiben.
Drifter
5
@drifter: An diesem Punkt öffnen Sie sich einer Welt voller Schmerzen. Sie verlassen sich auf aktuelle Implementierungsdetails, die sich in einer zukünftigen Version leicht ändern können.
Jon Skeet
11

Ich stimme den anderen Antworten darin zu, dass es allgemein und insbesondere mit dem Kommentar von E. Lippert funktioniert, dass dies kein dokumentiertes Verhalten und daher kein zukunftssicherer Code ist.

Wir haben jedoch auch ein anderes Problem festgestellt. Wenn Sie Ihren Code in einer Umgebung mit eingeschränkten Berechtigungen ausführen, wird möglicherweise eine Ausnahme angezeigt.

Wir hatten gerade einen Fall, in dem unser Code auf unseren Computern einwandfrei funktionierte, aber wir haben einen erhalten, VerificationExceptionals der Code in einer eingeschränkten Umgebung ausgeführt wurde. Der Täter war ein Reflexionsaufruf an den Setter eines schreibgeschützten Feldes. Es hat funktioniert, als wir die schreibgeschützte Einschränkung dieses Feldes aufgehoben haben.

Andreas
quelle
2
Würde mich interessieren, welche Umgebungen VerificationException
Sergey Zhukov
4

Sie haben gefragt, warum Sie die Kapselung so aufheben möchten.

Ich verwende eine Entity-Helfer-Klasse, um Entities zu hydratisieren. Dies verwendet Reflection, um alle Eigenschaften einer neuen leeren Entität abzurufen, und ordnet den Eigenschafts- / Feldnamen der Spalte in der Ergebnismenge zu und setzt ihn mit propertyinfo.setvalue ().

Ich möchte nicht, dass jemand anderes den Wert ändern kann, aber ich möchte auch nicht alle Anstrengungen unternehmen, um benutzerdefinierte Code-Hydratationsmethoden für jede Entität zu erstellen.

Meine vielen meiner gespeicherten Prozesse geben Ergebnismengen zurück, die nicht direkt Tabellen oder Ansichten entsprechen, sodass die ORMs des Codegens nichts für mich tun.

Nekroposter
quelle
1
Ich habe es auch verwendet, um einige API-Einschränkungen zu überwinden, bei denen der Wert entweder fest codiert war oder eine Konfigurationsdatei benötigte, die ich nicht bereitstellen konnte. (WSE 2.0-Dateigröße für DIME-Anhänge, wenn die Assemblys beispielsweise über Reflection geladen wurden)
StingyJack
3

Eine andere einfache Möglichkeit, dies mit unsicher zu tun (oder Sie können das Feld über DLLImport an eine C-Methode übergeben und dort festlegen).

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
quelle
2

Die Antwort lautet ja, aber was noch wichtiger ist:

Warum willst du? Das absichtliche Unterbrechen der Kapselung scheint mir eine schrecklich schlechte Idee zu sein.

Das Verwenden der Reflexion zum Ändern eines schreibgeschützten oder konstanten Feldes ist wie das Kombinieren des Gesetzes der unbeabsichtigten Konsequenzen mit dem Murphyschen Gesetz .

Powerlord
quelle
1
Die Antwort lautet "nur Neugier", wie oben erwähnt.
Ron Klein
Es gibt Zeiten, in denen ich genau diesen Trick ausführen muss, um den bestmöglichen Code zu schreiben. Ein typisches
Beispiel
3
Ich benutze diesen Trick auch in Unit-Test-Projekten, um Standardwerte zu überschreiben, die in keinem Geschäftscode geändert werden sollten ...
Koen
Und ich habe versucht, private interne Eigenschaften in den Basisklassenbibliotheken zu Testzwecken festzulegen, insbesondere die Mitgliedschafts-API, bei der MS alles als privat, intern und ohne Setter für Eigenschaften markiert hat. Es gibt Fälle dafür, aber Sie sind richtig, wenn die Frage auf eine API unter Ihrer Kontrolle angewendet wird
Chad Grant
3
Es gibt Situationen, in denen es Sinn macht. O / R-Mapper wie NHibernate tun dies ständig für die Hydratation, da nur so die Datenkapselung für persistente Entitäten überhaupt implementiert werden kann.
Chris
2

Tu das nicht.

Ich habe gerade einen Tag damit verbracht, einen surrealen Fehler zu beheben, bei dem Objekte nicht von ihrem eigenen deklarierten Typ sein konnten.

Das Ändern des schreibgeschützten Feldes hat einmal funktioniert. Wenn Sie jedoch versuchen, es erneut zu ändern, erhalten Sie folgende Situationen:

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

Also tu es nicht.

Dies war auf der Mono-Laufzeit (Unity-Game-Engine).

Tynan Sylvester
quelle
2
Zu Ihrer Information - Die Unity Engine kann nicht verwendet werden, um tiefgreifende sprachspezifische C # -Fragen wie diese effektiv zu beantworten, da Unity eine eigene Kompilierung von C # durchführt, als ob die .cs in gewissem Sinne Skripte wären. Ich sage nicht, dass Ihr Punkt nicht gültig ist, aber er ist sicherlich spezifisch für die Unity Engine & C # zusammen.
Dave Jellison
0

Ich möchte nur hinzufügen, dass Sie Folgendes verwenden können, wenn Sie dieses Zeug für Unit-Tests ausführen müssen:

A) Die PrivateObject- Klasse

B) Sie benötigen weiterhin eine PrivateObject-Instanz, können jedoch mit Visual Studio "Accessor" -Objekte generieren. Gewusst wie: Private Accessoren neu generieren

Wenn Sie private Felder eines Objekts in Ihrem Code außerhalb von Unit-Tests festlegen, ist dies eine Instanz von "Code-Geruch". Ich denke, dass der einzige andere Grund, warum Sie dies tun möchten, darin besteht, dass Sie sich mit einem Dritten befassen Bibliothek und Sie können den Zielklassencode nicht ändern. Selbst dann möchten Sie wahrscheinlich den Dritten kontaktieren, Ihre Situation erklären und prüfen, ob er seinen Code nicht ändert, um Ihren Anforderungen gerecht zu werden.

Dudeman3000
quelle