36 votos

C# No se puede utilizar ref u out parámetro dentro de un método anónimo cuerpo

Estoy tratando de crear una función que puede crear una Acción que los incrementos de cualquier entero es pasado. Sin embargo, mi primer intento me da un error "no se puede utilizar ref u out parámetro dentro de un método anónimo cuerpo".

public static class IntEx {
    public static Action CreateIncrementer(ref int reference) {
        return () => {
            reference += 1;
        };
    }
}

Entiendo por qué el compilador no le gusta esto, pero sin embargo, me gustaría tener una graciosa manera de proporcionar una agradable incrementer la fábrica que puede apuntar a cualquier número entero. La única manera en que estoy viendo de 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, que es más de un dolor para la persona que llama a utilizar; exigir a la persona que llama a crear dos lambdas en lugar de sólo pasa una referencia. Es más elegante manera de proporcionar esta funcionalidad, o sólo tengo que vivir con los dos-lambda opción?

25voto

Dax Fohl Puntos 3616

Bueno, he encontrado que en realidad es posible con punteros si la inseguridad del contexto:

public static class IntEx {
    unsafe public static Action CreateIncrementer(int* reference) {
        return () => {
            *reference += 1;
        };
    }
}

Sin embargo, el recolector de basura puede causar estragos con esta moviendo su referencia durante la recolección de la basura, como a continuación se 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 conseguir alrededor de este problema por la fijación de la variable a un punto específico en la memoria. Esto se puede hacer mediante la adición de la siguiente constructor:

    public Program() {
        GCHandle.Alloc(_i, GCHandleType.Pinned);
    }

Que mantiene el recolector de basura de mover objetos alrededor, exactamente lo que estamos buscando. Sin embargo, a continuación, tienes que agregar un destructor para liberar el pin, y fragmentos de la memoria durante toda la vida del objeto. No es realmente fácil. Esto tendría más sentido en C++, donde las cosas no cambian, y la gestión de los recursos es por el curso, pero no tanto en C# donde todo lo que se supone debe ser automática.

Por lo que parece que la moraleja de la historia es, simplemente envuelva ese miembro int en un tipo de referencia y hacer con ella.

(Y sí, esa es la manera en que yo tenía trabajo antes de hacer la pregunta, pero sólo estaba tratando de averiguar si había una manera de que yo pueda deshacerse de todo mi Referencia<int> variables miembro y sólo tiene que utilizar enteros normales. Oh, bueno.)

19voto

SLaks Puntos 391154

Esto no es posible.

El compilador va a transformar todas las variables locales y parámetros utilizados por los métodos anónimos en los campos de generado automáticamente el cierre de la clase.

El CLR no permitir ref tipos para ser almacenados en los campos.

Por ejemplo, si se pasa de un tipo de valor en una variable local como tal ref parámetro, el valor de la vida se extienden más allá de su marco de pila.

2voto

supercat Puntos 25534

Podría haber sido una característica útil para el tiempo de ejecución para permitir la creación de referencias a variables con un mecanismo para evitar que su persistencia; esta característica habría permitido a un indizador se comportan como una matriz (por ejemplo, para un Diccionario<Int32, de Punto> se puede acceder a través de "midiccionario[5].X = 9;"). Creo que esta característica podría haber sido de manera segura si tales referencias no podía ser abatido a otros tipos de objetos, ni utilizados como campos, ni se pasa por referencia a sí mismos (desde cualquier lugar en el que dicha referencia puede ser almacenada iría fuera del ámbito de aplicación antes de la referencia a sí mismo). Por desgracia, el CLR no proporciona una función.

Para implementar lo que está después requeriría que el llamador de cualquier función que utiliza un parámetro de referencia dentro de un cierre se debe ajustar dentro de un cierre de cualquier variable que se quiere pasar a una función de este tipo. Si hubo una declaración especial para indicar que un parámetro podría ser utilizado en una moda, puede ser práctico para un compilador para implementar el comportamiento necesario. Tal vez en una .net 5.0 compilador, aunque no estoy seguro de lo útil que sería.

BTW, a mi entender es de que los cierres en Java uso por el valor de la semántica, mientras que la de las .net son por referencia. Puedo entender que algunos usos ocasionales para su referencia semántica, pero usando de referencia por defecto parece una dudosa decisión, de forma análoga a la utilización de incumplimiento de un parámetro de referencia-que pasa semántica para VB versiones a través de VB6. Si uno quiere capturar el valor de una variable a la hora de crear un delegado para llamar a una función (por ejemplo, si uno quiere un delegado para llamar a MyFunction(X) utilizando el valor de X cuando el que se crea el delegado), es mejor usar un lambda con un extra de temp, o es mejor utilizar simplemente un delegado de fábrica y no se molestan con las expresiones Lambda.

Iteramos.com

Iteramos es una comunidad de desarrolladores que busca expandir el conocimiento de la programación mas allá del inglés.
Tenemos una gran cantidad de contenido, y también puedes hacer tus propias preguntas o resolver las de los demás.

Powered by: