una tipos programacion pasar parametros objetos metodos metodo instanciar funciones estructura consola como clases clase c# function c#-7.0

tipos - Funciones locales en C#: ¿capturar o no capturar cuando se pasan parámetros?



tipos de metodos en programacion (2)

Fue una pregunta interesante. Primero he descompilado la salida de compilación.

public int MultiplyFoo(int id) { return LocalFunctionTests./u003CMultiplyFoo/u003Eg__LocalBar/u007C0_0(id); } public int MultiplyBar(int id) { LocalFunctionTests./u003C/u003Ec__DisplayClass1_0 cDisplayClass10; cDisplayClass10.id = id; return LocalFunctionTests./u003CMultiplyBar/u003Eg__LocalBar/u007C1_0(ref cDisplayClass10); }

Cuando pasa id como parámetro, la función local es llamada con el parámetro id pasado. Nada sofisticado y el parámetro se almacena en el marco de pila del método. Sin embargo, si no pasa el parámetro, se crea una estructura (un pensamiento llamado ''clase'' como lo señaló Daisy) con un campo (cDisplayClass10.id = id) y se le asigna el ID. Luego la estructura se pasa como referencia a la función local. C # compilador parece hacerlo para apoyar el cierre.

En cuanto al rendimiento, utilicé Stopwatch.ElapsedTicks, pasar id como parámetro fue siempre más rápido. Creo que es por el costo de crear una estructura con un campo. La brecha de rendimiento se amplió cuando agregué otro parámetro a la función local.

  • Identificación de paso: 2247
  • No pasa la identificación: 2566

Este es mi código de prueba, si alguien está interesado.

public int MultiplyFoo(int id, int id2) { return LocalBar(id, id2); int LocalBar(int number, int number2) { return number * number2 * 2; } } public int MultiplyBar(int id, int id2) { return LocalBar(); int LocalBar() { return id * id2 * 2; } } [Fact] public void By_Passing_Id() { var sut = new LocalFunctions(); var watch = Stopwatch.StartNew(); for (int i = 0; i < 10000; i++) { sut.MultiplyFoo(i, i); } _output.WriteLine($"Elapsed: {watch.ElapsedTicks}"); } [Fact] public void By_Not_Passing_Id() { var sut = new LocalFunctions(); var watch = Stopwatch.StartNew(); for (int i = 0; i < 10000; i++) { sut.MultiplyBar(i, i); } _output.WriteLine($"Elapsed: {watch.ElapsedTicks}"); }

Cuando usa las funciones locales en C # 7, tiene dos opciones cuando quiere pasar parámetros (u otras variables locales) desde el método principal hasta la función local: puede declarar explícitamente los parámetros como lo haría con cualquier otra función o simplemente puede "capture" los parámetros / variables del método que contiene y utilícelos directamente.

Un ejemplo quizás ilustra esto mejor:

Declarando explícitamente

public int MultiplyFoo(int id) { return LocalBar(id); int LocalBar(int number) { return number * 2; } }

Capturando

public int MultiplyFoo(int id) { return LocalBar(); int LocalBar() { return id * 2; } }

Ambos métodos funcionan de la misma manera, pero la forma en que invocan la función local es diferente.

Así que mi pregunta es:

¿Hay alguna diferencia entre los dos que debería tener en cuenta? Estoy pensando en términos de rendimiento, asignación de memoria, recolección de basura, mantenimiento, etc.


Las funciones locales en C # son inteligentes en términos de su captura, al menos en la implementación de Roslyn. Cuando el compilador puede garantizar que no está creando un delegado desde la función local (o haciendo otra cosa que prolongará la vida útil de la variable) puede usar un parámetro ref con todas las variables capturadas en una estructura generada para comunicarse Con la función local. Por ejemplo, tu segundo método terminaría como algo como:

public int MultiplyFoo(int id) { __MultiplyFoo__Variables variables = new __MultiplyFoo__Variables(); variables.id = id; return __Generated__LocalBar(ref variables); } private struct __MultiplyFoo__Variables { public int id; } private int __Generated__LocalBar(ref __MultiplyFoo__Variables variables) { return variables.id * 2; }

Por lo tanto, no se requiere una asignación de almacenamiento dinámico, ya que habría (por ejemplo) una expresión lambda convertida en un delegado. Por otro lado, está la construcción de la estructura y luego copiar los valores en eso. Si pasar un int por valor es más o menos eficiente que pasar la estructura por referencia es poco probable que sea significativo ... aunque supongo que en los casos en que tenías una estructura enorme como una variable local, significaría que usar la captura implícita sería Más eficiente que usar un parámetro de valor simple. (Del mismo modo, si su función local utiliza muchas variables locales capturadas).

La situación ya se vuelve más complicada cuando tiene varias variables locales capturadas por diferentes funciones locales, y más aún cuando algunas de ellas son funciones locales dentro de los bucles, etc. Explorar con ildasm o Reflector puede ser bastante entretenido.

Tan pronto como comience a hacer algo complicado, como escribir métodos asíncronos, bloques de iteradores, expresiones lambda dentro de las funciones locales, usar conversiones de grupos de métodos para crear un delegado de la función local, etc. En ese momento, dudaría en seguir adivinando. Puede intentar comparar el código en cada dirección o mirar el IL, o simplemente escribir el código que sea más sencillo y confiar en sus pruebas de validación de rendimiento más grandes (que ya tiene, ¿verdad? :) para informarle si es un problema .