¿Puedo cambiar un campo privado de solo lectura en C # usando la reflexión?

115

Me pregunto, dado que se pueden hacer muchas cosas mediante la reflexión, ¿puedo cambiar un campo privado de solo lectura después de que el constructor haya completado su ejecución?
(nota: solo curiosidad)

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
fuente

Respuestas:

151

Usted puede:

typeof(Foo)
   .GetField("bar",BindingFlags.Instance|BindingFlags.NonPublic)
   .SetValue(foo,567);
Philippe Leybaert
fuente
2
Tiene toda la razón, por supuesto. Mis disculpas. Y sí, lo intenté, pero intenté establecer una propiedad de solo lectura directamente, sin usar un campo de respaldo. Lo que intenté no tenía sentido. Su solución funciona perfectamente bien (probado de nuevo, correctamente esta vez)
Sage Pourpre
¿cómo podemos hacer esto con moq?
l --''''''--------- '' '' '' '' '' ''
En dotnet core 3.0 esto ya no es posible. Se lanza una System.FieldAccessException que dice: "No se puede establecer 'barra' de campo estático de inicio solo después de que se inicializa el tipo 'Foo'".
David se presenta el
54

Lo obvio es probarlo:

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

Esto funciona bien. (Java tiene reglas diferentes, curiosamente, debe establecer explícitamente Fieldque sea accesible, y de todos modos solo funcionará para campos de instancia).

Jon Skeet
fuente
4
Ahmed, pero no estamos usando el idioma para hacerlo, por lo que la especificación del idioma no obtiene un voto ...
Marc Gravell
4
Sí, se puede hacer mucho que "rompe" lo que quiere el idioma. Puede ejecutar inicializadores de tipo varias veces, por ejemplo.
Jon Skeet
28
También observo que el hecho de que pueda en alguna implementación hoy no significa que pueda en cada implementación para siempre. No conozco ningún lugar donde documentemos que los campos de solo lectura deben ser mutables mediante la reflexión. Hasta donde yo sé, una implementación conforme de la CLI es perfectamente libre para implementar campos de solo lectura de modo que arrojen excepciones cuando se mutan a través de la reflexión después de que finaliza el constructor.
Eric Lippert
3
Sin embargo, eso no es bueno, porque hay casos en los que necesito extender una clase más de lo que fue diseñado originalmente. Siempre debe haber una forma de anular la encapsulación cuando está bien planificada. Las únicas alternativas son sangrientas y, a veces, no son posibles sin reescribir parte del marco.
vagabundo
5
@drifter: En ese punto, te estás abriendo a un mundo de dolor. Está confiando en los detalles de implementación actuales que pueden cambiar fácilmente en una versión futura.
Jon Skeet
11

Estoy de acuerdo con las otras respuestas en que funciona en general y especialmente con el comentario de E. Lippert de que este no es un comportamiento documentado y, por lo tanto, no es un código a prueba de futuro.

Sin embargo, también notamos otro problema. Si está ejecutando su código en un entorno con permisos restringidos, es posible que obtenga una excepción.

Acabamos de tener un caso en el que nuestro código funcionó bien en nuestras máquinas, pero recibimos un mensaje VerificationExceptioncuando el código se ejecutó en un entorno restringido. El culpable fue una llamada de reflexión al creador de un campo de solo lectura. Funcionó cuando eliminamos la restricción de solo lectura de ese campo.

Andreas
fuente
2
Estaría interesado en saber qué entornos arrojan VerificationException
Sergey Zhukov
4

Preguntó por qué querría romper la encapsulación de esa manera.

Utilizo una clase auxiliar de entidad para hidratar entidades. Esto usa la reflexión para obtener todas las propiedades de una nueva entidad vacía, y hace coincidir el nombre de la propiedad / campo con la columna en el conjunto de resultados, y lo establece usando propertyinfo.setvalue ().

No quiero que nadie más pueda cambiar el valor, pero tampoco quiero hacer todo el esfuerzo para personalizar los métodos de hidratación del código para cada entidad.

Muchos de mis procesos almacenados devuelven conjuntos de resultados que no se corresponden directamente con tablas o vistas, por lo que los ORM de generación de código no hacen nada por mí.

Necroposter
fuente
1
También lo he usado para superar algunas limitaciones de la API en las que el valor estaba codificado o requería un archivo de configuración que no podría proporcionar. (Tamaño de archivo WSE 2.0 para adjuntos DIME cuando los ensamblajes se cargaron mediante reflexión, por ejemplo)
StingyJack
3

Otra forma simple de hacer esto usando inseguro (o podría pasar el campo a un método C a través de DLLImport y configurarlo allí).

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

La respuesta es sí, pero lo más importante:

¿Por qué querrías hacerlo? Romper la encapsulación intencionalmente me parece una idea horriblemente mala.

Usar la reflexión para cambiar un campo de solo lectura o constante es como combinar la Ley de Consecuencias No Deseadas con la Ley de Murphy .

Señor de poder
fuente
1
la respuesta es "solo curiosidad", como se mencionó anteriormente.
Ron Klein
Hay momentos en los que tengo que hacer este truco para escribir el mejor código que pueda. Caso en cuestión - elegantcode.com/2008/04/17/testing-a-membership-provider
sparker
3
Yo también uso este truco en proyectos de pruebas unitarias para anular los valores predeterminados que no deben cambiarse en ningún código comercial ...
Koen
Y estaba intentando establecer propiedades internas privadas en las bibliotecas de clases base para fines de prueba, específicamente la API de membresía, donde MS marcaba todo como privado, interno y sin establecedores en las propiedades. Hay casos para esto, pero tiene razón si la pregunta se aplicó a una API bajo su control
Chad Grant
3
Hay situaciones en las que tiene sentido. Los mapeadores de O / R como NHibernate lo hacen todo el tiempo para la hidratación, porque esta es la única forma en que puede implementar la encapsulación de datos para entidades persistentes en primer lugar.
chris
2

No hagas esto.

Acabo de pasar un día solucionando un error surrealista donde los objetos podrían no ser de su propio tipo declarado.

La modificación del campo de solo lectura funcionó una vez. Pero si intentara modificarlo nuevamente, obtendría situaciones como esta:

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

Así que no lo hagas.

Esto fue en el tiempo de ejecución de Mono (motor de juego de Unity).

Tynan Sylvester
fuente
2
Para su información: el motor de Unity no se puede utilizar para responder de manera efectiva preguntas profundas específicas del lenguaje C # como esta porque Unity realiza su propia compilación de C # como si los .cs fueran scripts, en cierto sentido. No estoy diciendo que su punto no sea válido, pero ciertamente es específico de Unity Engine y C # juntos.
Dave Jellison
0

Solo quiero agregar que si necesita hacer estas cosas para las pruebas unitarias, puede usar:

A) La clase PrivateObject

B) Aún necesitará una instancia de PrivateObject, pero puede generar objetos "Accessor" con Visual Studio. Cómo: regenerar accesos privados

Si está configurando campos privados de un objeto en su código fuera de las pruebas unitarias, esa sería una instancia de "olor de código". Creo que quizás la única otra razón por la que querría hacer esto es si está tratando con un tercero biblioteca y no puede cambiar el código de la clase de destino. Incluso entonces, probablemente desee ponerse en contacto con el tercero, explicar su situación y ver si no siguen adelante y cambian su código para satisfacer sus necesidades.

Dudeman3000
fuente