Start Debugging
2023-10-31 Actualizado 2023-11-05 dotnetdotnet-8 Edit on GitHub

C# UnsafeAccessor: miembros privados sin reflexión (.NET 8)

Usa el atributo `[UnsafeAccessor]` en .NET 8 para leer campos privados y llamar a métodos privados sin sobrecarga, sin reflexión y totalmente compatible con AOT.

La reflexión te permite obtener información de tipos en tiempo de ejecución y acceder a miembros privados de una clase usando esa información. Esto puede ser especialmente útil al trabajar con clases que están fuera de tu control, proporcionadas por un paquete de terceros. Aunque es potente, la reflexión también es muy lenta, lo que es uno de los principales motivos para no usarla. Eso se acabó.

.NET 8 introduce una nueva forma de acceder a miembros privados sin sobrecarga mediante el uso del atributo UnsafeAccessor. El atributo se puede aplicar a un método extern static. La implementación del método la proporciona el runtime basándose en la información del atributo y la firma del método. Si no se encuentra coincidencia para la información proporcionada, la llamada al método lanzará una MissingFieldException o una MissingMethodException.

Veamos algunos ejemplos de cómo usar UnsafeAccessor. Consideremos la siguiente clase con miembros privados:

class Foo
{
    private Foo() { }
    private Foo(string value) 
    {
        InstanceProperty = value;
    }

    private string InstanceProperty { get; set; } = "instance-property";
    private static string StaticProperty { get; set; } = "static-property";

    private int instanceField = 1;
    private static int staticField = 2;

    private string InstanceMethod(int value) => $"instance-method:{value}";
    private static string StaticMethod(int value) => $"static-method:{value}";
}

Crear instancias de objeto usando constructores privados

Como se describió arriba, empezamos declarando los métodos static extern.

Deberías terminar con esto:

[UnsafeAccessor(UnsafeAccessorKind.Constructor)]
extern static Foo PrivateConstructor();

[UnsafeAccessor(UnsafeAccessorKind.Constructor)]
extern static Foo PrivateConstructorWithParameters(string value);

Crear instancias de objeto usando los constructores privados es trivial a partir de este punto.

var instance1 = PrivateConstructor();
var instance2 = PrivateConstructorWithParameters("bar");

Invocar métodos privados de instancia

El primer argumento del método extern static será una instancia de objeto del tipo que contiene el método privado. El resto de los argumentos deben coincidir con la firma del método al que estamos apuntando. El tipo de retorno también debe coincidir.

[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "InstanceMethod")]
extern static string InstanceMethod(Foo @this, int value);

Console.WriteLine(InstanceMethod(instance1, 42)); 
// Output: "instance-method:42"

Leer / escribir propiedades privadas de instancia

Notarás que no existe UnsafeAccessorKind.Property. Eso es porque, igual que con los métodos de instancia, las propiedades de instancia se acceden a través de sus métodos getter y setter:

[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "get_InstanceProperty")]
extern static string InstanceGetter(Foo @this);

[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "set_InstanceProperty")]
extern static void InstanceSetter(Foo @this, string value);

Console.WriteLine(InstanceGetter(instance1));
// Output: "instance-property"

InstanceSetter(instance1, "bar");

Console.WriteLine(InstanceGetter(instance1));
// Output: "bar"

Métodos y propiedades estáticos

Se comportan de forma idéntica a los miembros de instancia, con la única diferencia de que tienes que especificar UnsafeAccessorKind.StaticMethod en el atributo UnsafeAccessor. Incluso necesitas proporcionar una instancia de objeto de ese tipo al hacer la llamada.

¿Y las clases static? Las clases estáticas no son compatibles actualmente con UnsafeAccessor. Hay una propuesta de API que pretende cubrir este vacío, apuntando a .NET 9: [API Proposal]: UnsafeAccessorTypeAttribute for static or private type access

[UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "StaticMethod")]
extern static string StaticMethod(Foo @this, int value);

[UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "get_StaticProperty")]
extern static string StaticGetter(Foo @this);

[UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "set_StaticProperty")]
extern static void StaticSetter(Foo @this, string value);

Campos privados

Los campos son un poco más especiales en cuanto a la sintaxis del método extern static. Ya no tenemos métodos getter y setter disponibles, así que en su lugar usaremos la palabra clave ref para obtener una referencia al campo que podemos usar tanto para leer como para escribir el valor.

[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "instanceField")]
extern static ref int InstanceField(Foo @this);

[UnsafeAccessor(UnsafeAccessorKind.StaticField, Name = "staticField")]
extern static ref int StaticField(Foo @this);

// Read the field value
var x = InstanceField(instance1);
var y = StaticField(instance1);

// Update the field value
InstanceField(instance1) = 3;
StaticField(instance1) = 4;

¿Quieres probar esta característica? Puedes encontrar todos los ejemplos anteriores en GitHub.

Comments

Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.

< Volver