una - C#No se puede usar el parámetro ref o out dentro de un cuerpo de método anónimo
pase de parametros por referencia c# (3)
Intento crear una función que pueda crear una acción que incremente el entero que se pasa. Sin embargo, mi primer intento me da un error "no puedo usar el parámetro ref o out dentro de un cuerpo de método anónimo".
public static class IntEx {
public static Action CreateIncrementer(ref int reference) {
return () => {
reference += 1;
};
}
}
Entiendo por qué al compilador no le gusta esto, pero me gustaría tener una manera elegante de proporcionar una buena fábrica incremental que pueda apuntar a cualquier número entero. La única forma en que veo hacer esto es algo como lo siguiente:
public static class IntEx {
public static Action CreateIncrementer(Func<int> getter, Action<int> setter) {
return () => setter(getter() + 1);
}
}
Pero, por supuesto, eso es más doloroso de usar; requiriendo que la persona que llama cree dos lambdas en lugar de solo pasar una referencia. ¿Hay alguna forma más elegante de proporcionar esta funcionalidad, o tendré que vivir con la opción de dos lambda?
De acuerdo, he descubierto que en realidad es posible con punteros si se trata de un contexto inseguro:
public static class IntEx {
unsafe public static Action CreateIncrementer(int* reference) {
return () => {
*reference += 1;
};
}
}
Sin embargo, el recolector de basura puede causar estragos al mover su referencia durante la recolección de basura, ya que lo siguiente indica:
class Program {
static void Main() {
new Program().Run();
Console.ReadLine();
}
int _i = 0;
public unsafe void Run() {
Action incr;
fixed (int* p_i = &_i) {
incr = IntEx.CreateIncrementer(p_i);
}
incr();
Console.WriteLine(_i); // Yay, incremented to 1!
GC.Collect();
incr();
Console.WriteLine(_i); // Uh-oh, still 1!
}
}
Uno puede evitar este problema fijando la variable en un punto específico de la memoria. Esto se puede hacer agregando lo siguiente al constructor:
public Program() {
GCHandle.Alloc(_i, GCHandleType.Pinned);
}
Eso evita que el recolector de basura mueva el objeto, exactamente lo que estamos buscando. Sin embargo, debes agregar un destructor para liberar el pin, y fragmenta la memoria durante toda la vida útil del objeto. No es realmente más fácil. Esto tendría más sentido en C ++, donde las cosas no se mueven, y la gestión de recursos es el mismo, pero no tanto en C # donde todo lo que se supone es automático.
Así que parece que la moraleja de la historia es, simplemente envuelva ese miembro int en un tipo de referencia y termine con eso.
(Y sí, así es como lo tenía funcionando antes de hacer la pregunta, pero solo estaba tratando de averiguar si había una forma de que pudiera deshacerme de todas mis variables de miembro de referencia <int> y simplemente usar las constantes regulares. Oh, bueno. )
Esto no es posible.
El compilador transformará todas las variables locales y los parámetros utilizados por métodos anónimos en campos en una clase de cierre generada automáticamente.
CLR no permite que los tipos de ref
se almacenen en los campos.
Por ejemplo, si pasa un tipo de valor en una variable local como dicho parámetro ref
, la duración del valor se extendería más allá de su marco de pila.
Podría haber sido una característica útil para el tiempo de ejecución permitir la creación de referencias de variables con un mecanismo para evitar su persistencia; tal característica habría permitido que un indexador se comportara como una matriz (por ejemplo, se podía acceder a un Dictionary <Int32, Point> a través de "myDictionary [5] .X = 9;"). Creo que tal característica podría haber sido proporcionada de manera segura si tales referencias no pudieran ser downcast a otros tipos de objetos, ni usadas como campos, ni pasadas por referencia (ya que cualquier lugar donde tal referencia pueda ser almacenada saldría del alcance antes de la referencia sí mismo lo haría). Desafortunadamente, el CLR no proporciona esa característica.
Para implementar lo que está buscando, se requiere que el que llama de cualquier función que utiliza un parámetro de referencia dentro de un cierre debe envolver dentro de un cierre cualquier variable que quiera pasar a dicha función. Si hubiera una declaración especial para indicar que un parámetro se usaría de esa manera, podría ser práctico para un compilador implementar el comportamiento requerido. Tal vez en un compilador .NET 5.0, aunque no estoy seguro de lo útil que sería.
Por cierto, tengo entendido que los cierres en Java usan semántica de valor por defecto, mientras que los de .net son de referencia. Puedo entender algunos usos ocasionales de la semántica de referencia por referencia, pero el uso de la referencia por defecto parece una decisión dudosa, análoga al uso de la semántica de paso de parámetros por referencia por defecto para las versiones de VB hasta VB6. Si uno quiere capturar el valor de una variable al crear un delegado para llamar a una función (por ejemplo, si uno quiere que un delegado llame a MyFunction (X) usando el valor de X cuando se crea el delegado), ¿es mejor usar un lambda? con una temperatura extra, o es mejor simplemente usar una fábrica de delegados y no molestarse con las expresiones de Lambda.