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

173

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

Je suis tombé sur l'erreur aujourd'hui et j'ai trouvé une solution de contournement, mais j'étais toujours curieux de savoir pourquoi il s'agit d'une erreur de compilation.

CS1628 : Impossible d'utiliser le paramètre "paramètre" dans ref ou out dans une méthode anonyme, une expression lambda ou 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();
}
skalb
la source
Il s'agit d'itérateurs, mais une grande partie du même raisonnement dans cet article (également par Eric Lippert & mdash; il fait partie de l'équipe de conception du langage après tout) s'applique aux lambdas: < blogs.msdn.com/ericlippert/archive/2009/07/13 /… >
Joel Coehoorn
17
Puis-je vous demander quelle a été la solution de contournement que vous avez trouvée?
Beatles1692
3
Vous pouvez simplement déclarer une variable normale locale et travailler avec cela, et affecter le résultat à value par la suite ... Ajoutez un var tempValue = value; puis travaillez avec tempValue.
Drunken Code Monkey

Réponses:

122

Les lambdas ont l'apparence de changer la durée de vie des variables qu'ils capturent. Par exemple , l'expression lambda suivant provoque le paramètre p1 à vivre plus long que le cadre de la méthode actuelle en tant que sa valeur est accessible après la trame de procédé ne sont plus sur la pile

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

Une autre propriété des variables capturées est que les modifications apportées à la variable sont également visibles en dehors de l'expression lambda. Par exemple les impressions suivantes 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. Envisagez de passer une variable locale en tant que paramètre ref à une fonction.
  • Les effets secondaires dans le lambda devraient être visibles sur le paramètre ref lui-même. À la fois dans la méthode et dans l'appelant.

Ce sont des propriétés quelque peu incompatibles et c'est l'une des raisons pour lesquelles elles ne sont pas autorisées dans les expressions lambda.

JaredPar
la source
36
Je comprends que nous ne pouvons pas utiliser à l' refintérieur de l'expression lambda, mais le désir de l'utiliser n'a pas été nourri.
zionpi
85

Sous le capot, la méthode anonyme est implémentée en soulevant les variables capturées (ce qui est le but de votre corps de question) et en les stockant en tant que champs d'une classe générée par le compilateur. Il n'existe aucun moyen de stocker un paramètre refou en outtant que champ. Eric Lippert en a discuté dans une entrée de blog . Notez qu'il existe une différence entre les variables capturées et les paramètres 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);
}
Mehrdad Afshari
la source
70

Vous pouvez mais vous devez définir explicitement tous les types afin

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

Est invalide, cependant

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

Est valable

Ben Adams
la source
13
Cela fait; la question est pourquoi vous ne pouvez pas; la réponse est que vous pouvez.
Ben Adams
24
Ce n'est pas le cas; question est pourquoi vous ne pouvez pas référencer une variable existante , déjà définie refou out, à l'intérieur d'un lambda. Il est clair si vous lisez l'exemple de code (essayez à nouveau de le relire). La réponse acceptée explique clairement pourquoi. Votre réponse concerne l'utilisation refou le out paramètre du lambda. Totalement ne pas répondre à la question et parler d'autre chose
edc65
4
@ edc65 a raison ... cela n'a rien à voir avec le sujet de la question, qui concerne le contenu de l'expression lamba (à droite), pas sa liste de paramètres (à gauche). C'est bizarre que cela ait obtenu 26 votes positifs.
Jim Balter
6
Cela m'a aidé cependant. +1 pour ça. Merci
Emad
1
Mais je ne comprends toujours pas pourquoi il a été conçu pour être comme ça. Pourquoi dois-je définir explicitement tous les types? Sémantiquement, je n'en ai pas besoin. Est-ce que je perds quelque chose?
joe
5

Comme il s'agit de l'un des meilleurs résultats pour "C # lambda ref" sur Google; Je sens que je dois développer les réponses ci-dessus. L'ancienne syntaxe de délégué anonyme (C # 2.0) fonctionne et prend en charge des signatures plus complexes (ainsi que des fermetures). Les délégués lambda et anonymes ont au moins partagé l'implémentation perçue 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 lorsque j'ai fait la recherche, pour démontrer la syntaxe:

public static ScanOperation<TToken> CreateScanOperation(
    PrattTokenDefinition<TNode, TToken, TParser, TSelf> tokenDefinition)
{
    var oldScanOperation = tokenDefinition.ScanOperation; // Closures still work.
    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ûres d'un point de vue procédural et mathématique (en raison de la promotion de la valeur de référence mentionnée précédemment): vous pouvez ouvrir une boîte de vers. Réfléchissez bien lorsque vous utilisez cette syntaxe.

Jonathan Dickinson
la source
3
Je pense que vous avez mal compris la question. La question était de savoir pourquoi un lambda ne pouvait pas accéder aux variables ref / out dans sa méthode de conteneur, pas pourquoi le lambda lui - même ne pouvait pas contenir de variables ref / out. AFAIK il n'y a aucune bonne raison pour ce dernier. Aujourd'hui, j'ai écrit un lambda (a, b, c, ref d) => {...}et j'ai refété souligné en rouge avec le message d'erreur "Le paramètre '4' doit être déclaré avec le mot clé 'ref'". Facepalm! PS qu'est-ce que la "promotion de la valeur ref"?
Qwertie
1
@Qwertie J'ai obtenu que cela fonctionne avec un paramétrage complet, c'est-à-dire inclure les types sur a, b, c et d et cela fonctionne. Voir la réponse de BenAdams (bien qu'il comprenne également mal la question initiale).
Ed Bayiates
@Qwertie Je pense que je n'ai supprimé que la moitié de ce point - je pense que le point d'origine était que placer des paramètres de référence dans une fermeture pouvait ê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 non plus Je sais si cela compilerait même).
Jonathan Dickinson
Cela n'a rien à voir avec la question réellement posée ... voir la réponse acceptée et les commentaires sous la réponse de Ben Adams, qui a également mal compris la question.
Jim Balter
1

Et peut-être ça?

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();
}
chatouilleux
la source