Posso modificare un campo di sola lettura privato in C # utilizzando la reflection?


115

Mi chiedo, dal momento che molte cose possono essere fatte usando la riflessione, posso cambiare un campo di sola lettura privato dopo che il costruttore ha completato la sua esecuzione?
(nota: solo curiosità)

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

Risposte:


151

Puoi:

typeof(Foo)
   .GetField("bar",BindingFlags.Instance|BindingFlags.NonPublic)
   .SetValue(foo,567);

2
Hai assolutamente ragione, ovviamente. Mie scuse. E sì, ho provato, ma ho tentato di impostare direttamente una proprietà di sola lettura, non utilizzando un campo di supporto. Quello che ho tentato non aveva senso. La tua soluzione funziona perfettamente (testata di nuovo, correttamente questa volta)
Sage Pourpre

come possiamo farlo con moq?
l --''''''--------- '' '' '' '' '' ''

In dotnet core 3.0 questo non è più possibile. Viene generata un'eccezione System.FieldAccessException che dice: "Impossibile impostare il campo statico" bar "solo all'inizio dopo l'inizializzazione del tipo" Foo "."
David Perfors

54

La cosa ovvia è provarlo:

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

Funziona bene. (Java ha regole diverse, interessante: devi impostare esplicitamente Fieldche sia accessibile e funzionerà comunque solo per i campi di istanza.)


4
Ahmed - ma non stiamo usando la lingua per farlo, quindi le specifiche della lingua non ottengono un voto ...
Marc Gravell

4
Sì, c'è molto che si può fare per "spezzare" ciò che la lingua vuole. Ad esempio, puoi eseguire gli inizializzatori di tipo più volte.
Jon Skeet

28
Noto anche che solo perché puoi farlo in qualche implementazione oggi non significa che puoi farlo su ogni implementazione per sempre. Non sono a conoscenza di alcun luogo in cui documentiamo che i campi di sola lettura devono essere mutabili tramite la riflessione. Per quanto ne so, un'implementazione conforme della CLI è perfettamente libera di implementare campi di sola lettura in modo tale che generino eccezioni quando vengono mutati tramite riflessione dopo che il costruttore è stato eseguito.
Eric Lippert

3
Non va bene, però, perché ci sono casi in cui ho bisogno di estendere una classe più di quanto era stato originariamente progettato per essere. Ci dovrebbe essere sempre un modo per ignorare l'incapsulamento quando è ben pianificato. Le uniche alternative sono cruente e talvolta non è possibile senza riscrivere parte del framework.
drifter

5
@drifter: A quel punto, ti stai aprendo a un mondo di dolore. Ti affidi ai dettagli di implementazione attuali che possono facilmente cambiare in una versione futura.
Jon Skeet

11

Sono d'accordo con le altre risposte in quanto funziona in generale e soprattutto con il commento di E. Lippert che questo non è un comportamento documentato e quindi non un codice a prova di futuro.

Tuttavia, abbiamo notato anche un altro problema. Se stai eseguendo il codice in un ambiente con autorizzazioni limitate, potresti ottenere un'eccezione.

Abbiamo appena avuto un caso in cui il nostro codice funzionava bene sulle nostre macchine, ma abbiamo ricevuto un messaggio VerificationExceptionquando il codice è stato eseguito in un ambiente limitato. Il colpevole era una chiamata di riflessione al setter di un campo di sola lettura. Ha funzionato quando abbiamo rimosso la restrizione di sola lettura di quel campo.


2
Sarei interessato a sapere quali ambienti generano VerificationException
Sergey Zhukov

4

Hai chiesto perché vorresti rompere l'incapsulamento in quel modo.

Uso una classe helper di entità per idratare le entità. Questo utilizza la riflessione per ottenere tutte le proprietà di una nuova entità vuota e abbina il nome della proprietà / campo alla colonna nel gruppo di risultati e lo imposta usando propertyinfo.setvalue ().

Non voglio che nessun altro sia in grado di modificare il valore, ma non voglio nemmeno dedicare tutto lo sforzo ai metodi di idratazione del codice personalizzati per ogni entità.

Molti dei miei processi memorizzati restituiscono set di risultati che non corrispondono direttamente a tabelle o viste, quindi gli ORM di generazione del codice non fanno nulla per me.


1
L'ho anche usato per superare alcune limitazioni api in cui il valore era hardcoded o richiedeva un file di configurazione che non sarei stato in grado di fornire. (Dimensione del file WSE 2.0 per gli allegati DIME quando gli assembly sono stati caricati tramite reflection, ad esempio)
StingyJack

3

Un altro modo semplice per farlo utilizzando unsafe (oppure potresti passare il campo a un metodo C tramite DLLImport e impostarlo lì).

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

2

La risposta è sì, ma ancora più importante:

Perché dovresti volerlo? Rompere intenzionalmente l'incapsulamento mi sembra un'idea orribilmente cattiva.

Usare la riflessione per modificare un campo di sola lettura o costante è come combinare la Legge delle conseguenze indesiderate con la Legge di Murphy .


1
la risposta è "solo curiosità", come detto sopra.
Ron Klein

Ci sono momenti in cui mi trovo a dover fare proprio questo trucco per scrivere il miglior codice possibile. Caso in questione - elegantcode.com/2008/04/17/testing-a-membership-provider
sparker

3
Anch'io uso questo trucco nei progetti di unit test per sovrascrivere i valori predefiniti che non dovrebbero essere modificati in alcun codice aziendale ...
Koen

E stavo tentando di impostare proprietà interne private nelle librerie della classe base a scopo di test, in particolare l'API di appartenenza, dove MS ha contrassegnato tutto ciò che è privato, interno e senza setter sulle proprietà. Ci sono casi per questo, ma hai ragione se la domanda è stata applicata a un'API sotto il tuo controllo
Chad Grant

3
Ci sono situazioni in cui ha senso. I mappatori O / R come NHibernate lo fanno sempre per l'idratazione, perché questo è l'unico modo per implementare l'incapsulamento dei dati per entità persistenti in primo luogo.
chris

2

Non farlo.

Ho appena passato una giornata a correggere un bug surreale in cui gli oggetti potrebbero non essere del loro tipo dichiarato.

La modifica del campo di sola lettura ha funzionato una volta. Ma se provassi a modificarlo di nuovo, otterrai situazioni come questa:

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

Quindi non farlo.

Questo era nel runtime Mono (motore di gioco Unity).


2
Cordiali saluti, Unity Engine non può essere utilizzato per rispondere efficacemente a domande specifiche del linguaggio C # profonde come questa perché Unity esegue la propria compilazione di C # come se i .cs fossero script, in un certo senso. Non sto dicendo che il tuo punto non sia valido, ma è certamente specifico per Unity Engine e C # insieme.
Dave Jellison

0

Voglio solo aggiungere che se hai bisogno di fare queste cose per i test unitari, puoi usare:

A) La classe PrivateObject

B) Avrai ancora bisogno di un'istanza PrivateObject, ma puoi generare oggetti "Accessor" con Visual Studio. Procedura: rigenerare funzioni di accesso private

Se imposti campi privati ​​di un oggetto nel tuo codice al di fuori del test unitario, sarebbe un'istanza di "odore di codice" Penso che forse l'unico altro motivo per cui vorresti farlo è se hai a che fare con una terza parte libreria e non puoi modificare il codice della classe di destinazione. Anche in questo caso, probabilmente vorrai contattare la terza parte, spiegare la tua situazione e vedere se non andranno avanti e cambieranno il loro codice per soddisfare le tue necessità.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.