30 votes

Fonctions locales en C # - pour capturer ou ne pas capturer lors du passage des paramètres?

Lors de l'utilisation Locale des Fonctions en C# 7, vous avez deux options lorsque vous voulez passer des paramètres (ou d'autres variables locales) à partir de la méthode main vers le bas pour la fonction local: Vous pouvez déclarer explicitement les paramètres comme vous le feriez pour toute autre fonction, ou vous pouvez tout simplement "capturer" les paramètres/variables à partir de la méthode et les utiliser directement.

Un exemple peut-être l'illustre le mieux:

Déclarer Explicitement

public int MultiplyFoo(int id)
{
    return LocalBar(id);

    int LocalBar(int number)
    {
        return number * 2;
    }
}

La capture

public int MultiplyFoo(int id)
{
    return LocalBar();

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

Les deux méthodes fonctionnent de la même, mais la façon de les invoquer la fonction locale est différente.

Donc ma question est:

Il y a aucune différence entre les deux que je devrais être au courant? Je suis en train de penser en termes de performances, de l'allocation de mémoire, la collecte des ordures, l'entretien etc.

19voto

Jon Skeet Points 692016

Local des fonctions en C# sont intelligents en termes de capture - au moins dans le Roslyn mise en œuvre. Quand le compilateur est capable de garantir que vous n'êtes pas de la création d'un délégué de la fonction locale (ou faire autre chose qui permettra de prolonger la durée de vie de la variable) il peut utiliser ref paramètre avec toutes les saisies de variables dans une générés struct pour communiquer avec la fonction local. Par exemple, votre deuxième méthode finirait en tant que quelque chose comme:

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;
}

Donc, il n'y a pas d'allocation de tas nécessaire qu'il y en aurait pour (dire) d'une expression lambda converti à un délégué. D'autre part, il y a la construction de la structure, puis de copier les valeurs dans la. Si le passage d'un int , en valeur, est plus ou moins efficace que le passage de la structure par la référence est peu probable d'être important... bien que je pense que dans le cas où vous avez eu une énorme structure comme une variable locale, cela voudrait dire que l'utilisation implicite de capture serait plus efficace que la simple utilisation d'un paramètre de valeur. (Même si votre fonction local utilisé beaucoup de la capture de variables locales.)

La situation déjà devient plus compliqué lorsque vous avez plusieurs variables locales d'être capturé par les différentes fonctions locales - et encore plus quand certains de ceux qui sont des fonctions locales dans les boucles etc. Explorer avec ildasm ou un Réflecteur, etc peuvent être assez divertissant.

Dès que vous commencez à faire quelque chose de compliqué, comme l'écriture de méthodes asynchrones, itérateur, les blocs, les expressions lambda au sein de l'fonctions locales, l'aide de la méthode du groupe de conversions pour créer un délégué de la fonction locale, etc... à ce stade, je n'hésiterais pas à continuer de le deviner. Vous pouvez soit essayer de référence le code dans chaque sens, ou de regarder le IL, ou tout simplement écrire selon le code est plus simple et comptons sur votre plus grande performance des tests de validation (qui, vous l'avez déjà? :) pour vous permettre de savoir si c'est un problème.

5voto

Andy Points 1118

C'était une question intéressante. Tout d'abord j'ai décompilé le construire de sortie.

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);
}

Lorsque vous passez à l'id en paramètre, la fonction appelée avec l'id passé en paramètre. Rien de compliqué, et le paramètre est stocké sur le frame de pile de la méthode. Toutefois, si vous ne passez pas sur le paramètre, un struct (pensée nommée "classe" que Daisy a fait remarquer) est créé avec un champ (cDisplayClass10.id = id) et l'id lui est attribué. Ensuite, la structure est passée en tant que référence dans le local de la fonction. Compilateur C# semble le faire à l'appui de la fermeture.

En terme de performance, j'ai utilisé un Chronomètre.ElapsedTicks, en passant l'id en paramètre, et a été constamment plus rapide. Je pense que c'est parce que le coût de la création d'une structure avec un champ. L'écart de rendement augmentait lorsque j'ai ajouté un autre paramètre à la fonction locale.

  • En Passant Id: 2247
  • Pas de passage Id: 2566

C'est mes tests de code, si ça intéresse quelqu'un

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}");
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X