ventajas usar tipos tipo runtimebinderexception runtimebinder puede porque microsoft metodos invocar funcion ejemplo delegate delegados delegado csharp anonimos c# delegates expression-trees setvalue fieldinfo

c# - usar - ¿Hay una manera de crear un delegado para obtener y establecer valores para un FieldInfo?



tipos de delegates (6)

Para las propiedades hay GetGetMethod y GetSetMethod para que pueda hacer:

Getter = (Func<S, T>)Delegate.CreateDelegate(typeof(Func<S, T>), propertyInfo.GetGetMethod());

y

Setter = (Action<S, T>)Delegate.CreateDelegate(typeof(Action<S, T>), propertyInfo.GetSetMethod());

Pero, ¿cómo voy sobre FieldInfo s?

No estoy buscando delegados para GetValue y SetValue (lo que significa que SetValue reflexión cada vez)

Getter = s => (T)fieldInfo.GetValue(s); Setter = (s, t) => (T)fieldInfo.SetValue(s, t);

¿Pero si hay un enfoque CreateDelegate aquí? Quiero decir, ya que las asignaciones devuelven un valor , ¿puedo tratar las asignaciones como un método? Si es así, ¿hay un manejador de MethodInfo para ello? En otras palabras, ¿cómo MethodInfo el MethodInfo de MethodInfo correcto MethodInfo establecer y obtener un valor de un campo miembro al método CreateDelegate para que CreateDelegate obtener un delegado con el que pueda leer y escribir directamente en los campos?

Getter = (Func<S, T>)Delegate.CreateDelegate(typeof(Func<S, T>), fieldInfo.??); Setter = (Action<S, T>)Delegate.CreateDelegate(typeof(Action<S, T>), fieldInfo.??);

Puedo construir expresiones y compilarlas, pero estoy buscando algo más simple. Al final, no me importa seguir la ruta de la expresión si no hay respuesta para la pregunta , como se muestra a continuación:

var instExp = Expression.Parameter(typeof(S)); var fieldExp = Expression.Field(instExp, fieldInfo); Getter = Expression.Lambda<Func<S, T>>(fieldExp, instExp).Compile(); if (!fieldInfo.IsInitOnly) { var valueExp = Expression.Parameter(typeof(T)); Setter = Expression.Lambda<Action<S, T>>(Expression.Assign(fieldExp, valueExp), instExp, valueExp).Compile(); }

¿O estoy después de lo inexistente (ya que no he visto algo como eso todavía)?


Aquí hay otra opción para crear un delegado cuando trabaja con objetos (no sabe el tipo específico de un campo). Aunque es más lento si el campo es una estructura (debido al boxeo).

public static class ReflectionUtility { public static Func<object, object> CompileGetter(this FieldInfo field) { string methodName = field.ReflectedType.FullName + ".get_" + field.Name; DynamicMethod setterMethod = new DynamicMethod(methodName, typeof(object), new[] { typeof(object) }, true); ILGenerator gen = setterMethod.GetILGenerator(); if (field.IsStatic) { gen.Emit(OpCodes.Ldsfld, field); gen.Emit(field.FieldType.IsClass ? OpCodes.Castclass : OpCodes.Box, field.FieldType); } else { gen.Emit(OpCodes.Ldarg_0); gen.Emit(OpCodes.Castclass, field.DeclaringType); gen.Emit(OpCodes.Ldfld, field); gen.Emit(field.FieldType.IsClass ? OpCodes.Castclass : OpCodes.Box, field.FieldType); } gen.Emit(OpCodes.Ret); return (Func<object, object>)setterMethod.CreateDelegate(typeof(Func<object, object>)); } public static Action<object, object> CompileSetter(this FieldInfo field) { string methodName = field.ReflectedType.FullName + ".set_" + field.Name; DynamicMethod setterMethod = new DynamicMethod(methodName, null, new[] { typeof(object), typeof(object) }, true); ILGenerator gen = setterMethod.GetILGenerator(); if (field.IsStatic) { gen.Emit(OpCodes.Ldarg_1); gen.Emit(field.FieldType.IsClass ? OpCodes.Castclass : OpCodes.Unbox_Any, field.FieldType); gen.Emit(OpCodes.Stsfld, field); } else { gen.Emit(OpCodes.Ldarg_0); gen.Emit(OpCodes.Castclass, field.DeclaringType); gen.Emit(OpCodes.Ldarg_1); gen.Emit(field.FieldType.IsClass ? OpCodes.Castclass : OpCodes.Unbox_Any, field.FieldType); gen.Emit(OpCodes.Stfld, field); } gen.Emit(OpCodes.Ret); return (Action<object, object>)setterMethod.CreateDelegate(typeof(Action<object, object>)); } }


Como sugirió Peter Ritchie, puede compilar su propio código en tiempo de ejecución. El método se compilará tan pronto como invoque al delegado por primera vez. Por lo tanto, la primera llamada será lenta, pero cualquier llamada posterior será tan rápida como pueda obtener en .NET sin punteros / uniones no administrados. Excepto por la primera llamada, el delegado es aproximadamente 500 veces más rápido que FieldInfo directamente.

class DemoProgram { class Target { private int value; } static void Main(string[] args) { FieldInfo valueField = typeof(Target).GetFields(BindingFlags.NonPublic| BindingFlags.Instance).First(); var getValue = CreateGetter<Target, int>(valueField); var setValue = CreateSetter<Target, int>(valueField); Target target = new Target(); setValue(target, 42); Console.WriteLine(getValue(target)); } static Func<S, T> CreateGetter<S, T>(FieldInfo field) { string methodName = field.ReflectedType.FullName + ".get_" + field.Name; DynamicMethod setterMethod = new DynamicMethod(methodName, typeof(T), new Type[1] { typeof(S) }, true); ILGenerator gen = setterMethod.GetILGenerator(); if (field.IsStatic) { gen.Emit(OpCodes.Ldsfld, field); } else { gen.Emit(OpCodes.Ldarg_0); gen.Emit(OpCodes.Ldfld, field); } gen.Emit(OpCodes.Ret); return (Func<S, T>)setterMethod.CreateDelegate(typeof(Func<S, T>)); } static Action<S, T> CreateSetter<S,T>(FieldInfo field) { string methodName = field.ReflectedType.FullName+".set_"+field.Name; DynamicMethod setterMethod = new DynamicMethod(methodName, null, new Type[2]{typeof(S),typeof(T)},true); ILGenerator gen = setterMethod.GetILGenerator(); if (field.IsStatic) { gen.Emit(OpCodes.Ldarg_1); gen.Emit(OpCodes.Stsfld, field); } else { gen.Emit(OpCodes.Ldarg_0); gen.Emit(OpCodes.Ldarg_1); gen.Emit(OpCodes.Stfld, field); } gen.Emit(OpCodes.Ret); return (Action<S, T>)setterMethod.CreateDelegate(typeof(Action<S, T>)); } }

Tenga en cuenta que las estructuras se pasan por valor. Eso significa que una Action<S, T> no se puede usar para cambiar miembros de una estructura si se pasa por valor como el primer argumento.


El acceso a los campos no se realiza a través de un método (como captadores y definidores), se realiza con una instrucción IL, por lo que no hay nada que pueda asignar a un delegado. Tendrá que usar la ruta de expresión para crear un "bloque" de código (efectivamente IL) que puede asignarse a un delegado.


El uso de la nueva función " ref return " en C # 7.0 puede hacer que el proceso de creación y uso de los generadores de acceso get / set generados dinámicamente sea mucho más simple y sintácticamente transparente. En lugar de tener que usar DynamicMethod para emitir funciones separadas de getter y setter para acceder al campo, ahora puede tener un solo método que devuelva una referencia de tipo puntero administrado al campo, esencialmente un único elemento de acceso que (a su vez) lo habilita. -hoc obtener a̲n̲d̲ establecer acceso. A continuación, proporciono una función de utilidad auxiliar que simplifica la generación de una función de obtención ByRef para cualquier campo de instancia arbitrario (es decir, privado) en cualquier clase.

Para “solo el código”, salte a la nota a continuación.

Como ejemplo de ejecución, digamos que queremos acceder a un campo de instancia privado m_iPrivate , un int definido en la clase OfInterestClass :

public class OfInterestClass { private int m_iPrivate; };

A continuación, asumamos que tenemos una función de " OfInterestClass referencia" de campo estático que toma una instancia de OfInterestClass y devuelve el valor del campo deseado por referencia utilizando la nueva capacidad de " retorno de referencia " de C # 7 (a continuación, proporcionaré el código para generar dichas funciones en tiempo de ejecución, a través de DynamicMethod ):

public static ref int __refget_m_iPrivate(this OfInterestClass obj) { /// ... }

Dicha función ("ref-getter", digamos) es todo lo que necesitamos para tener acceso completo de lectura / escritura al campo privado. En los siguientes ejemplos, tenga en cuenta especialmente la operación de invocación del establecedor, y las demostraciones de uso de los operadores (es decir, ++ y += , ya que aplicar esos operadores directamente a una llamada de método puede parecer un poco inusual si no está activo a velocidad en C # 7 .

void MyFunction(OfInterestClass oic) { int the_value = oic.__refget_m_iPrivate(); // ''get'' oic.__refget_m_iPrivate() = the_value + 100; // ''set'' /// or simply... oic.__refget_m_iPrivate() += 100; // <-- yes, you can oic.__refget_m_iPrivate()++; // <-- this too, no problem ref int prv = ref oic.__refget_m_iPrivate(); // via "ref-local" in C#7 prv++; foo(ref prv); // all of these directly affect… prv = 999; // …field m_iPrivate ''in-situ'' }

Como es el punto, cada operación que se muestra en estos ejemplos manipula m_iPrivate in situ (es decir, directamente dentro de su instancia oic contenedora) de modo que cualquiera y todos los cambios sean públicamente visibles allí inmediatamente. Es importante darse cuenta de que esto significa que prv , a pesar de estar int tipeado y declarado localmente, no se comporta como su variable típica "local". Esto es especialmente significativo para el código concurrente; los cambios no solo son visibles b̲e̲f̲o̲r̲e MyFunction ha salido, sino que ahora con C # 7 , los llamadores tienen la capacidad de retener un puntero administrado de retorno de ref (como un ref local ) y así continuar modificando el objetivo por un tiempo arbitrariamente largo.

Por supuesto, una ventaja principal y obvia de usar un puntero administrado aquí (y en cualquier lugar en general) es que siempre permanece válido, incluso como oic (una instancia de tipo de referencia asignada en el montón de GC se puede mover durante la recolección de basura. Esta es una diferencia gigantesca frente a los punteros nativos.

Como se describió anteriormente, el ref-getter es un método de extensión static que se puede declarar y / o usar desde cualquier lugar. Pero si puede crear su propia clase derivada de OfInterestClass (es decir, si OfInterestClass no está sealed ), puede hacerlo aún más agradable. En una clase derivada, puede exponer la sintaxis de C # para usar el campo privado de la clase base como si fuera un campo público de su clase derivada. Para hacer esto, simplemente agregue una propiedad de devolución de referencia de solo lectura de C # a su clase que vincule el método de obtención de referencias estático a la instancia actual:

public ref int m_iPrivate => ref __refget_m_iPrivate(this);

Aquí, la propiedad se hace public para que cualquiera pueda acceder al campo (a través de una referencia a nuestra clase derivada). Básicamente hemos publicado públicamente el campo privado de la clase base. Ahora, en la clase derivada (o en cualquier otro lugar, según corresponda), puede realizar cualquiera de las siguientes acciones:

int v = m_iPrivate; // get the value m_iPrivate = 1234; // set the value m_iPrivate++; // increment it ref int pi = ref m_iPrivate; // reference as C# 7 ref local v = Interlocked.Exchange(ref m_iPrivate, 9999); // even do in-situ atomic operations on it!

Como puede ver, debido a que la propiedad , como el método anterior, también tiene un valor de retorno por referencia , se comporta casi exactamente como lo hace un campo.

Así que ahora para los detalles. ¿Cómo creas la función estática de ref-getter que mostré arriba? Usando DynamicMethod , esto debería ser trivial. Por ejemplo, aquí está el código IL para una función getter estática tradicional (por valor):

// static int get_iPrivate(OfInterestClass oic) => oic.m_iPrivate; IL_0000: ldarg.0 IL_0001: ldfld Int32 m_iPrivate/OfInterestClass IL_0006: ret

Y aquí está el código IL que queremos en su lugar (ref-return):

// static ref int refget_iPrivate(OfInterestClass oic) => ref oic.m_iPrivate; IL_0000: ldarg.0 IL_0001: ldfld̲a Int32 m_iPrivate/OfInterestClass IL_0006: ret

La única diferencia con el captador por valor es que estamos usando el código de operación ldfld (dirección del campo de carga) en lugar de ldfld (campo de carga). Entonces, si estás bien practicado con DynamicMethod no debería ser un problema, ¿verdad?

¡Incorrecto! ...
Desafortunadamente, DynamicMethod no permite un valor de retorno por referencia .

Si intenta llamar al constructor DynamicMethod especificando un tipo ByRef como el valor de retorno ...

var dm = new DynamicMethod( "", // method name typeof(int).MakeByRefType(), // by-ref return type <-- ERROR new[] { typeof(OfInterestClass) }, // argument type(s) typeof(OfInterestClass), // owner type true); // private access

... la función lanza la NotSupportedException con el siguiente mensaje:

El tipo de retorno contiene algún tipo no válido (es decir, nulo, ByRef)

Aparentemente, esta función no obtuvo la nota en C # 7 y ref-return. Afortunadamente, encontré una solución simple que lo hace funcionar. Si pasa un tipo no ref en el constructor como un "dummy" temporal, pero inmediatamente después, utilice la reflexión en la instancia DynamicMethod recién creada para cambiar su campo privado m_returnType para que sea el tipo de tipo ByRef ( sic. ) Que En realidad quiero, entonces todo parece funcionar bien.

Para acelerar el proceso, recortaré el método genérico completado que automatiza todo el proceso creando / devolviendo una función estática de obtención de refuerzos para el campo de instancia privada de tipo U , con el nombre proporcionado y definida en la clase T

Si solo desea el código de trabajo completo , copie desde debajo de este punto hasta el final

Primero tenemos que definir un delegado que represente al que Func<T,TResult> el ref, ya que no se puede declarar un delegado Func<T,TResult> con ByRef. Afortunadamente, la sintaxis de delegate más antigua funciona para hacerlo ( ¡uf! ).

public delegate ref U RefGetter<T, U>(T obj);

Coloque el delegado, junto con la siguiente función estática en una clase de utilidad centralizada donde se puede acceder a ambos a lo largo de su proyecto. Aquí está la función de creación de ref-getter final que se puede usar para crear un ref-getter estático para el campo de instancia denominado en cualquier clase.

public static RefGetter<T, U> create_refgetter<T, U>(String s_field) { const BindingFlags bf = BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly; var fi = typeof(T).GetField(s_field, bf); if (fi == null) throw new MissingFieldException(typeof(T).Name, s_field); var s_name = "__refget_" + typeof(T).Name + "_fi_" + fi.Name; // workaround for using ref-return with DynamicMethod: // a.) initialize with dummy return value var dm = new DynamicMethod(s_name, typeof(U), new[] { typeof(T) }, typeof(T), true); // b.) replace with desired ''ByRef'' return value dm.GetType().GetField("m_returnType", bf).SetValue(dm, typeof(U).MakeByRefType()); var il = dm.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldflda, fi); il.Emit(OpCodes.Ret); return (RefGetter<T, U>)dm.CreateDelegate(typeof(RefGetter<T, U>)); }

Volviendo al principio de este artículo, podemos proporcionar fácilmente la función __refget_m_iPrivate que hizo que todo comenzara. En lugar de una función estática escrita directamente en C #, usaremos la función de creación de ref-getter estática para crear el cuerpo de la función en tiempo de ejecución y almacenarla en un campo estático de tipo delegado (con la misma firma). La sintaxis para llamarlo en la propiedad de instancia (como se muestra arriba y se repite a continuación) o en otra parte es la misma que si el compilador hubiera podido escribir la función.

Finalmente, para almacenar en memoria caché el delegado ref-getter creado dinámicamente, coloque la siguiente línea en cualquier clase static de su elección. Reemplace OfInterestClass con el tipo de la clase base, int con el tipo de campo del campo privado y cambie el argumento de cadena para que coincida con el nombre del campo privado. Si no puede crear su propia clase derivada de OfInterestClass (o no quiere), está listo; simplemente haga public este campo y puede llamarlo como una función, pasando cualquier instancia de OfInterestClass para obtener una referencia que le permita leer, escribir o monitorear su campo private m_iPrivate " m_iPrivate ".

// Static delegate instance of ref-getter method, statically initialized. // Requires an ''OfInterestClass'' instance argument to be provided by caller. static RefGetter<OfInterestClass, int> __refget_m_iPrivate = create_refgetter<OfInterestClass, int>("m_iPrivate");

Opcionalmente, si desea publicar el campo oculto con una sintaxis más limpia o más natural, puede definir una clase de proxy (no estática) propia que contenga una instancia de, o incluso mejor (si es posible), deriva de —La clase que oculta el campo OfInterestClass. En lugar de implementar la línea de código que anteriormente se muestra globalmente en una clase static , colóquela en su clase de proxy y luego agregue la siguiente línea:

// optional: ref-getter as an instance property (no ''this'' argument required) public ref int m_iPrivate => ref __refget_m_iPrivate(this);


No sé si usarías Expression , entonces ¿por qué evitar la reflexión? La mayoría de las operaciones de Expression basan en la reflexión.

GetValue y SetValue son el get method y set method , para los campos, pero no para un campo específico.

Los campos no son como las propiedades, son campos y no hay razón para generar métodos get / set para cada uno. Sin embargo, el tipo puede variar según el campo y, por lo tanto, GetValue y SetValue se definen el parameter/return value como object para la varianza. GetValue es incluso un método abstracto, es decir, para cada clase (todavía reflexión) que lo reemplaza, debe estar dentro de la misma firma.

Si no los escribe , entonces el siguiente código debería hacer:

public static void SomeMethod(FieldInfo fieldInfo) { var Getter=(Func<object, object>)fieldInfo.GetValue; var Setter=(Action<object, object>)fieldInfo.SetValue; }

Pero si quieres, hay una forma restringida:

public static void SomeMethod<S, T>(FieldInfo fieldInfo) where S: class where T: class { var Getter=(Func<S, object>)fieldInfo.GetValue; var Setter=(Action<S, T>)fieldInfo.SetValue; }

Por la razón de que el Getter aún sea Func<S, object> , es posible que desee ver:

Covarianza y contraparte en C #, tercera parte: variación de conversión del grupo de métodos en el blog del Sr. Lippert.


No, no hay una manera fácil de crear un delegado para obtener / establecer un campo.

Tendrá que hacer su propio código para proporcionar esa funcionalidad. Sugeriría dos funciones en una biblioteca compartida para proporcionar esto.

Usando su código (en este ejemplo, solo muestro la creación del get-delegate):

static public class FieldInfoExtensions { static public Func<S, T> CreateGetFieldDelegate<S, T>(this FieldInfo fieldInfo) { var instExp = Expression.Parameter(typeof(S)); var fieldExp = Expression.Field(instExp, fieldInfo); return Expression.Lambda<Func<S, T>>(fieldExp, instExp).Compile(); } }

Esto facilita la creación de un get-delegate a partir de un FieldInfo (asumiendo que el campo es de tipo int):

Func<MyClass, int> getter = typeof(MyClass).GetField("MyField").CreateGetFieldDelegate<MyClass, int>();

O si cambiamos un poco tu código:

static public class TypeExtensions { static public Func<S, T> CreateGetFieldDelegate<S, T>(this Type type, string fieldName) { var instExp = Expression.Parameter(type); var fieldExp = Expression.Field(instExp, fieldName); return Expression.Lambda<Func<S, T>>(fieldExp, instExp).Compile(); } }

Esto lo hace aún más fácil:

Func<MyClass, int> getter = typeof(MyClass).CreateGetFieldDelegate<MyClass, int>("MyField");

También es posible crear estos delegados utilizando IL, pero ese código sería más complejo y no tendría mucho más rendimiento, si lo hubiera.