C # 7 Tupel und Lambdas

74

Ist es mit der neuen C # 7-Tupelsyntax möglich, ein Lambda mit einem Tupel als Parameter anzugeben und entpackte Werte innerhalb des Lambda zu verwenden?

Beispiel:

var list = new List<(int,int)>();

normale Art, ein Tupel in Lambda zu verwenden:

list.Select(value => value.Item1*2 + value.Item2/2);

Ich erwartete etwas neuen Zucker zu vermeiden .Item1 .Item2, wie:

list.Select((x,y) => x*2 + y/2);

Die letzte Zeile funktioniert nicht, da sie als zwei Parameter für Lambda behandelt wird. Ich bin mir nicht sicher, ob es tatsächlich einen Weg gibt, dies zu tun.

BEARBEITEN:

Ich habe versucht, doppelte Klammern in der Lambda-Definition zu verwenden, und es hat nicht funktioniert: ((x,y)) => ...und vielleicht war es dumm, es zu versuchen, aber doppelte Klammern funktionieren hier tatsächlich:

list.Add((1,2));

Außerdem geht es bei meiner Frage nicht ganz darum, hässliche Standardnamen zu vermeiden .Item .Item2, sondern darum, ein Tupel in Lambda tatsächlich zu entpacken (und vielleicht, warum es nicht implementiert oder nicht möglich ist). Wenn Sie hierher gekommen sind, um eine Lösung für Standardnamen zu finden, lesen Sie die Antwort von Sergey Berezovskiy .

EDIT 2:

Ich dachte nur an einen allgemeineren Anwendungsfall: Ist es möglich (oder warum nicht), ein an eine Methode übergebenes Tupel zu "dekonstruieren"? So was:

void Foo((int,int)(x,y)) { x+y; }

An Stelle von:

void Foo((int x,int y) value) { value.x+value.y }
Rast
quelle

Antworten:

51

Wie Sie beobachtet haben, für:

var list = new List<(int,int)>();

Man würde zumindest erwarten, folgendes tun zu können:

list.Select((x,y) => x*2 + y/2);

Der C # 7-Compiler unterstützt dies jedoch (noch) nicht. Es ist auch vernünftig, Zucker zu wünschen, der Folgendes zulässt:

void Foo(int x, int y) => ...

Foo(list[0]);

mit dem Compiler, Foo(list[0]);der Foo(list[0].Item1, list[0].Item2);automatisch konvertiert .

Beides ist derzeit nicht möglich. Das Problem " Vorschlag: Tupel-Dekonstruktion in der Lambda-Argumentliste" ist jedoch im dotnet/csharplangRepo auf GitHub vorhanden und fordert das Sprachteam auf, diese Funktionen für eine zukünftige Version von C # zu berücksichtigen. Bitte fügen Sie Ihre Stimmen zu diesem Thread hinzu, wenn auch Sie Unterstützung dafür sehen möchten.

David Arno
quelle
40

Sie sollten Namen von Tupeleigenschaften angeben (also, ValueTuple hat Felder), da sonst Standardnamen verwendet werden, wie Sie gesehen haben:

var list = new List<(int x, int y)>();

Jetzt haben Tupel schön benannte Felder, die Sie verwenden können

list.Select(t => t.x * 2 + t.y / 2)

Vergessen Sie nicht, das System.ValueTuple- Paket von NuGet hinzuzufügen, und beachten Sie, dass ValueTuples veränderbare Strukturen sind.


Update: Dekonstruktion wird derzeit nur als Zuordnung zu vorhandenen Variablen (Dekonstruktionszuweisung) oder zu neu erstellten lokalen Variablen (Dekonstruktionsdeklaration) dargestellt. Der anwendbare Auswahlalgorithmus für Funktionsmitglieder ist der gleiche wie zuvor:

Jedes Argument in der Argumentliste entspricht einem Parameter in der Funktionselementdeklaration, wie in §7.5.1.1 beschrieben, und jeder Parameter, dem kein Argument entspricht, ist ein optionaler Parameter.

Die Tupelvariable ist ein einzelnes Argument. Es kann nicht mehreren Parametern in der Liste der formalen Parameter der Methode entsprechen.

Sergey Berezovskiy
quelle
gut, aber es wird nur umbenannt, Werte sind immer noch Felder eines einzelnen Objekts, das an Lambda übergeben wird. Ich war neugierig, ob es eine Möglichkeit gibt, es in mehrere Variablen zu zerlegen.
Rast
1
@Rast: Meines Wissens funktioniert die Dekonstruktion von Tupeln nur in Kontexten, in denen Ihre Deklaration der Variablen die Zuweisung aus dem Tupel enthalten kann. Das würde eine Parameterliste ausschließen. Sergeys Beispiel scheint ziemlich sauber zu sein; Was passt nicht zu Ihren Bedürfnissen?
Peter Duniho
2
@ Peter Duniho es ist nur meine Neugier. Ich dachte, dass Dekonstruktion oder ähnliches in diesem Fall nützlich sein könnte.
Rast
@Rast Enumerable.Selecthat einen Func<TSource, TResult>Delegaten als Parameter. Es akzeptiert ein einzelnes Argument vom Typ TSource und Sie können das nicht ändern
Sergey Berezovskiy
1
@Rast: Vielleicht, aber nicht in einer Parameterliste, in der die Zuweisung von nicht standardmäßigen Werten nicht zulässig ist. Sie könnten stattdessen so etwas tun list.Select(t => { (int x, int y) = t; return x * 2 + y / 2 });, aber an diesem Punkt verlieren Sie meiner Meinung nach die Prägnanz der Tupelsyntax gegenüber der obigen Alternative.
Peter Duniho
11

Dekonstruktionen in C # 7.0 unterstützen drei Formen:

  • Dekonstruktionserklärung (wie (var x, var y) = e;),
  • Dekonstruktionszuweisung (wie (x, y) = e;),
  • und Dekonstruktion-foreach (wie foreach(var(x, y) in e) ...).

Andere Kontexte wurden berücksichtigt, haben jedoch wahrscheinlich einen abnehmenden Nutzen, und wir konnten sie im Zeitrahmen von C # 7.0 nicht abschließen. Die Dekonstruktion in einer let-Klausel ( let (x, y) = e ...) und in Lambdas scheint ein guter Kandidat für eine zukünftige Erweiterung zu sein.

Letzteres wird unter https://github.com/dotnet/csharplang/issues/258 erörtert

Bringen Sie dort Ihr Feedback und Ihr Interesse zum Ausdruck, da dies den Vorschlägen von Champions helfen wird.

Weitere Details zu dem, was in der C # 7.0-Dekonstruktion enthalten war, finden Sie im Konstruktionsdokument .

Julien Couvreur
quelle
4

Das Programm, auf das Sie stoßen, ist die Unfähigkeit des Compilers, auf den Typ in diesem Ausdruck zu schließen:

list.Select(((int x, int y) t) => t.x * 2 + t.y / 2);

Aber da (int, int)und (int x, int y)sind der gleiche CLR-Typ ( System.ValueType<int, int>), wenn Sie die Typparameter angeben:

list.Select<(int x, int y), int>(t => t.x * 2 + t.y / 2);

Es wird klappen.

Paulo Morgado
quelle