Posso alterar um campo somente leitura privado em C # usando reflexão?

115

Estou pensando, uma vez que muitas coisas podem ser feitas usando reflexão, posso alterar um campo somente leitura privado depois que o construtor concluiu sua execução?
(nota: apenas curiosidade)

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
fonte

Respostas:

151

Você pode:

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

O óbvio é tentar:

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

Isso funciona bem. (Java tem regras diferentes, curiosamente - você deve definir explicitamente o Fieldpara ser acessível e ele só funcionará para campos de instância de qualquer maneira.)

Jon Skeet
fonte
11

Concordo com as outras respostas no sentido de que funciona de maneira geral e especialmente com o comentário de E. Lippert de que este não é um comportamento documentado e, portanto, não é um código à prova de futuro.

No entanto, também notamos outro problema. Se você estiver executando seu código em um ambiente com permissões restritas, poderá obter uma exceção.

Acabamos de ter um caso em que nosso código funcionou bem em nossas máquinas, mas recebemos um VerificationExceptionquando o código foi executado em um ambiente restrito. O culpado foi uma chamada de reflexão para o setter de um campo somente leitura. Funcionou quando removemos a restrição somente leitura desse campo.

Andreas
fonte
4

Você perguntou por que deseja quebrar o encapsulamento assim.

Eu uso uma classe auxiliar de entidade para hidratar entidades. Isso usa reflexão para obter todas as propriedades de uma nova entidade vazia e corresponde o nome da propriedade / campo à coluna no conjunto de resultados e define-o usando propertyinfo.setvalue ().

Não quero que mais ninguém seja capaz de alterar o valor, mas também não quero me esforçar para personalizar métodos de hidratação de código para cada entidade.

Muitos dos meus procs armazenados retornam conjuntos de resultados que não correspondem diretamente a tabelas ou visualizações, portanto, o ORM do gerador de código não faz nada por mim.

Necroposter
fonte
3

Outra maneira simples de fazer isso usando não seguro (ou você pode passar o campo para um método C via DLLImport e defini-lo 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);
        }
    }
}
zezba9000
fonte
2

A resposta é sim, mas o mais importante:

Por que você iria querer? Romper o encapsulamento intencionalmente parece uma péssima ideia para mim.

Usar a reflexão para alterar um campo somente leitura ou constante é como combinar a Lei das Consequências Involuntárias com a Lei de Murphy .

Powerlord
fonte
2

Não faça isso.

Acabei de passar um dia consertando um bug surreal em que os objetos não podiam ser de seu próprio tipo declarado.

Modificar o campo somente leitura funcionou uma vez. Mas se você tentasse modificá-lo novamente, obteria situações como esta:

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

Portanto, não faça isso.

Isso foi no tempo de execução Mono (motor de jogo Unity).

Tynan Sylvester
fonte
0

Só quero acrescentar que, se precisar fazer isso para teste de unidade, você pode usar:

A) A classe PrivateObject

B) Você ainda precisará de uma instância de PrivateObject, mas pode gerar objetos "Accessor" com o Visual Studio. Como: regenerar acessores privados

Se você estiver definindo campos privados de um objeto em seu código fora do teste de unidade, isso seria uma instância de "cheiro de código". Acho que talvez a única outra razão pela qual você queira fazer isso é se você está lidando com um terceiro biblioteca e você não pode alterar o código de classe de destino. Mesmo assim, você provavelmente deseja entrar em contato com o terceiro, explicar sua situação e ver se eles não irão em frente e alterar seu código para acomodar sua necessidade.

Dudeman3000
fonte