185 votes

Impossible d'utiliser un paramètre ref ou out dans les expressions lambda

Pourquoi ne peut-on pas utiliser un paramètre ref ou out dans une expression lambda ?

J'ai rencontré cette erreur aujourd'hui et j'ai trouvé une solution de contournement mais j'étais quand même curieux de savoir pourquoi c'est une erreur de compilation.

CS1628: Impossible d'utiliser le paramètre 'parameter' en ref ou out à l'intérieur d'une méthode anonyme, d'une expression lambda ou d'une expression de requête

Voici un exemple simple :

private void Foo()
{
    int value;
    Bar(out value);
}

private void Bar(out int value)
{
    value = 3;
    int[] array = { 1, 2, 3, 4, 5 };
    int newValue = array.Where(a => a == value).First();
}

0 votes

Il s'agit d'itérateurs, mais une grande partie du raisonnement dans ce post (également par Eric Lippert — il est bien membre de l'équipe de conception du langage) s'applique également aux lambdas: <blogs.msdn.com/ericlippert/archive/2009/07/13/…>

18 votes

Puis-je demander quelle était la solution de contournement que vous aviez trouvée ?

3 votes

Vous pouvez simplement déclarer une variable normale locale et travailler avec celle-ci, puis attribuer le résultat à la valeur par la suite... Ajoutez une instruction var tempValue = valeur; puis travaillez avec tempValue.

129voto

JaredPar Points 333733

Les lambdas donnent l'impression de changer la durée de vie des variables qu'elles capturent. Par exemple, l'expression lambda suivante fait en sorte que le paramètre p1 persiste plus longtemps que le cadre de méthode actuel, car sa valeur peut être consultée après que le cadre de méthode ne soit plus sur la pile

Func Example(int p1) {
  return () => p1;
}

Une autre propriété des variables capturées est que les modifications apportées aux variables sont également visibles en dehors de l'expression lambda. Par exemple, le code suivant affiche 42

void Example2(int p1) {
  Action del = () => { p1 = 42; };
  del();
  Console.WriteLine(p1);
}

Ces deux propriétés produisent un certain ensemble d'effets qui vont à l'encontre d'un paramètre ref de la manière suivante:

  • Les paramètres ref peuvent avoir une durée de vie fixe. Considérez le passage d'une variable locale en tant que paramètre ref à une fonction.
  • Les effets secondaires dans la lambda doivent être visibles sur le paramètre ref lui-même. À la fois à l'intérieur de la méthode et chez l'appelant.

Ces propriétés sont quelque peu incompatibles et sont l'une des raisons pour lesquelles elles sont interdites dans les expressions lambda.

1 votes

Si vous voulez toujours l'utiliser, vous pouvez créer une variable temporaire et l'utiliser à l'intérieur de la lamda. quelque chose comme int tempVariable = refVariable; int newValue = array.Where(a => a == tempVariable).First();

87voto

Mehrdad Afshari Points 204872

Sous le capot, la méthode anonyme est implémentée en élevant les variables capturées (c'est de cela que traite le corps de votre question) et en les stockant en tant que champs d'une classe générée par le compilateur. Il n'y a aucun moyen de stocker un paramètre ref ou out en tant que champ. Eric Lippert en a discuté dans une entrée de blog. Notez qu'il y a une différence entre les variables capturées et les paramètres de lambda. Vous pouvez avoir des "paramètres formels" comme les suivants car ils ne sont pas des variables capturées :

delegate void TestDelegate (out int x);
static void Main(string[] args)
{
    TestDelegate testDel = (out int x) => { x = 10; };
    int p;
    testDel(out p);
    Console.WriteLine(p);
}

79voto

BenAdams Points 605

Vous le pouvez mais vous devez définir, de façon explicite, tous les types ainsi

(a, b, c, ref d) => {...}

Est invalide, cependant

(int a, int b, int c, ref int d) => {...}

Est valide

13 votes

Il le fait; la question est pourquoi ne pouvez-vous pas; la réponse est que vous le pouvez.

25 votes

Il ne le fait pas; la question est pourquoi vous ne pouvez pas faire référence à une variable existante, déjà définie ref ou out, à l'intérieur d'une lambda. C'est clair si vous lisez le code exemple (essayez de le relire). La réponse acceptée explique clairement pourquoi. Votre réponse concerne l'utilisation de ref ou out paramètre à la lambda. Ne répondant pas du tout à la question et parlant d'autre chose.

4 votes

@edc65 a raison ... cela n'a rien à voir avec le sujet de la question, qui porte sur le contenu de l'expression lambda (à droite), pas sur sa liste de paramètres (à gauche). C'est bizarre que cela ait reçu 26 votes favorables.

5voto

Jonathan Dickinson Points 4655

Comme il s'agit de l'un des principaux résultats pour "C# lambda ref" sur Google, je pense que je dois développer les réponses ci-dessus. La syntaxe des délégués anonymes plus ancienne (C# 2.0) fonctionne et prend en charge des signatures plus complexes (ainsi que des fermetures). Les lambdas et les délégués anonymes ont au moins une implémentation perçue partagée dans le backend du compilateur (s'ils ne sont pas identiques) - et surtout, ils prennent en charge les fermetures.

Ce que j'essayais de faire quand j'ai fait la recherche, pour démontrer la syntaxe :

public static ScanOperation CreateScanOperation(
    PrattTokenDefinition tokenDefinition)
{
    var oldScanOperation = tokenDefinition.ScanOperation; // Les fermetures continuent de fonctionner.
    return delegate(string text, ref int position, ref PositionInformation currentPosition)
        {
            var token = oldScanOperation(text, ref position, ref currentPosition);
            if (token == null)
                return null;
            if (tokenDefinition.LeftDenotation != null)
                token._led = tokenDefinition.LeftDenotation(token);
            if (tokenDefinition.NullDenotation != null)
                token._nud = tokenDefinition.NullDenotation(token);
            token.Identifier = tokenDefinition.Identifier;
            token.LeftBindingPower = tokenDefinition.LeftBindingPower;
            token.OnInitialize();
            return token;
        };
}

Gardez simplement à l'esprit que les Lambdas sont plus sûrs sur le plan procédural et mathématique (en raison de la promotion de la valeur ref mentionnée précédemment) : vous pourriez ouvrir une boîte de Pandore. Réfléchissez bien avant d'utiliser cette syntaxe.

3 votes

Je pense que vous avez mal compris la question. La question était pourquoi une lambda ne pouvait pas accéder aux variables ref/out dans sa méthode conteneur, pas pourquoi la lambda elle-même ne peut pas contenir de variables ref/out. À ma connaissance, il n'y a pas de bonne raison pour cela. Aujourd'hui, j'ai écrit une lambda (a, b, c, ref d) => {...} et le mot-clé ref était souligné en rouge avec le message d'erreur "Le paramètre '4' doit être déclaré avec le mot-clé 'ref'". Facepalm! P.S. qu'est-ce que la "promotion de valeur ref"?

1 votes

@Qwertie J'ai réussi à le faire fonctionner grâce à une pleine paramétrisation, c'est-à-dire, en incluant les types sur a, b, c et d, et ça marche. Voir la réponse de BenAdams (bien qu'il ait lui aussi mal compris la question originale).

0 votes

@Qwertie Je pense que j'ai seulement supprimé la moitié de ce point - je pense que le point original était que placer des paramètres de référence dans une fermeture pourrait être risqué, mais j'ai dû réaliser par la suite que cela ne se produisait pas dans l'exemple que j'ai donné (et je ne sais même pas si cela compilerait).

3voto

ticky Points 23

Et peut-être ceci?

private void Foo()
{
    int value;
    Bar(out value);
}

private void Bar(out int value)
{
    value = 3;
    int[] array = { 1, 2, 3, 4, 5 };
    var val = value; 
    int newValue = array.Where(a => a == val).First();
}

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