Verwendungszweck: Tupel gegen Klasse c # 7.0

76

Vor Tuples habe ich ein classund seine Variablen erstellt und dann ein Objekt aus dieser Klasse erstellt und dieses Objekt zum Rückgabetyp für einige Funktionen gemacht.

Jetzt mit den Tupeln kann ich das gleiche tun und in c # 7.0 wir verständliche Namen für Tupeln Eigenschaften zuordnen können (vorher war es item1, item2, etc ..)

Jetzt frage ich mich also, wann ich es verwenden soll und wann ich eine Klasse in c # 7.0 erstellen soll.

Madonna Remon
quelle
2
Beachten Sie, dass die Namen der Tupelelemente meist eine Entwurfszeit-Eigenschaft sind und in der kompilierten IL nicht vorhanden sind. Wenn Sie sich darum kümmern, haben Sie einen weiteren Grund für den richtigen Unterricht.
stupsen
3
@OrkhanAlikhanov Obwohl dies ein korrektes doppeltes Ziel wäre, stimme ich OP zu, dass sich die Zeiten jetzt mit C # 7 geändert haben, sodass die Antworten von 2012 und 2010 jetzt nur teilweise zutreffen.
stupsen
2
@poke, das kein gültiges doppeltes Ziel ist, diese Frage betraf System.Tuple , diese betrifft das neue System.ValueTuple, das speziellen syntaktischen Zucker in C # 7 enthält.
Theraot
@ Theraot Du merkst doch, dass ich den Vorschlag kritisiert habe, diese Frage zu schließen, oder? Ich weiß, worum es bei dieser Frage geht, weshalb ich vorgeschlagen habe, diese Frage offen zu lassen.
Poke
@Theraot Oh, ich habe gerade den gleichen Fehler bei der Interpretation dieser Frage gemacht wie bei der Bezugnahme auf System.Tuple, da dort steht ... "Tuple". Wenn Sie sicher sind, dass es sich tatsächlich um etwas anderes handelt, sollten Sie es bearbeiten, um dies klarer zu machen. Ich habe meine Schließung rückgängig gemacht, vorausgesetzt, Sie haben Recht.
Cody Gray

Antworten:

56

Da diese Antwort bei einigen Leuten hier einige Verwirrung stiftet, sollte ich klarstellen, dass sich - gemäß der Frage - alle Verweise auf "Tupel" hier auf den ValueTupleTyp und die neuen syntaktischen Tupelzuckermerkmale von C # 7 beziehen und sich in keiner Weise auf die alten beziehen System.TupleReferenztypen.

Jetzt frage ich mich also, wann ich Tupel verwenden und wann ich eine Klasse in c # 7.0 erstellen soll.

Nur Sie können diese Frage wirklich beantworten, da sie wirklich von Ihrem Code abhängt.

Es gibt jedoch Richtlinien und Regeln, die Sie bei der Auswahl befolgen können:

Tupel sind Werte und werden daher eher nach Wert als nach Referenz kopiert.

Meistens sollte dies kein Problem sein. Wenn Sie jedoch Tupel großer Strukturen herumgeben, kann dies Auswirkungen auf die Leistung haben. Ref-Einheimische / Retouren können jedoch verwendet werden, um diese Leistungsprobleme zu umgehen.

Da es sich um Werte handelt, wird durch Ändern einer Kopie aus der Ferne die Originalkopie nicht geändert. Dies ist eine gute Sache, könnte aber einige Leute herausholen.

Tupelelementnamen werden nicht beibehalten

Die Namen der Elemente werden vom Compiler verwendet und sind (in den meisten Fällen) zur Laufzeit nicht verfügbar. Dies bedeutet, dass Reflexion nicht verwendet werden kann, um ihre Namen zu entdecken. Auf sie kann nicht dynamisch zugegriffen werden, und sie können nicht in Rasiereransichten verwendet werden.

Auch dies ist eine wichtige Überlegung bei APIs. Ein von einer Methode zurückgegebenes Tupel ist die Ausnahme von der Regel bezüglich der Auffindbarkeit von Namen nach der Kompilierung. Der Compiler fügt der Methode Attribute hinzu, die Informationen zu den Tupelnamen enthalten. Dies bedeutet, dass Sie ein Tupel aus einer öffentlichen Methode in einer Assembly sicher zurückgeben und in einer anderen auf seine Namen zugreifen können.

Tupel sind leicht

Tupel sind viel einfacher zu schreiben als Typen, da sie weniger ausführlich sind und die Deklaration "inline" sein kann (dh am Verwendungsort deklariert wird). Dies funktioniert gut, wenn Sie beispielsweise eine Methode deklarieren, die mehrere Werte zurückgibt.

Da sie jedoch am Verwendungsort deklariert sind , müssen Sie das Tupel in jeder Phase neu definieren , wenn Sie MethodAdiese Aufrufe haben , MethodBdie aufrufen MethodCund jeweils ein Tupel zurückgeben. Es gibt ( noch ) keine Möglichkeit, einen Alias ​​eines Tupels zu erstellen und ihn über mehrere Methoden hinweg wiederzuverwenden.

Verwenden Sie einfach den gesunden Menschenverstand

Für jede Situation, in der Sie ein Tupel verwenden könnten: Stellen Sie sich einfach die Frage: "Wird ein Tupel den Code hier vereinfachen?". Wenn die Antwort "Ja" lautet, verwenden Sie eine. Und das ist letztendlich die wichtigste Überlegung, ob ein Tupel oder eine benutzerdefinierte Klasse verwendet werden soll.

David Arno
quelle
1
ValueTupleist Werttyp. Tupleist Referenztyp. So einfach wird Tuplenicht als Referenz kopiert. Sie sollten es fett erwähnen, weil es gerade verwirrend ist
Szer
2
@Szer, die Frage bezieht sich auf die Tupel von C # 7, daher verstehe ich nicht, warum es Verwirrung gibt. Aber ich habe die Frage aktualisiert, um diesen Punkt zu klären.
David Arno
@DavidArno Als ich anfing, C # 7 zu verwenden, war mir nicht klar, ob die neue Tupel-Syntax ein neuer Typ oder nur Sprachunterstützung war.
Gusdor
Ich bin überrascht, dass Tupelelementnamen in Razor-Ansichten nicht verwendet werden konnten. Werden Razor-Dateien nicht in C # konvertiert und dann kompiliert? In diesem Fall sollten die Tupelelementnamen aus Metadaten / IL angezeigt werden, genau wie bei jeder Clientkompilierung.
Julien Couvreur
1
@ JulienCouvreur, soweit ich weiß, ist es eine Einschränkung der Rasierwerkzeuge. Nach dem Aussehen von github.com/aspnet/Razor/issues/1046 ist dies jetzt entweder behoben oder es wird bald ein Fix veröffentlicht.
David Arno
25

Im Allgemeinen haben benannte Klassen eine gewisse Bedeutung für das Design Ihres Systems. Sie sind auch ausführlicher zu schreiben. Beispielsweise kann eine Klasse aufgerufen werden MediaFileOpener. Für das Design ist es wichtig, dass wir wissen, was diese Klasse tut - wir arbeiten mit Mediendateien!

Anonyme Typen und Tupel werden verwendet, wenn keine Entwurfsbedeutung vorliegt und Sie lediglich ein leichtes Datenübertragungsobjekt (DTO) zum Verschieben von Informationen benötigen.

Verwenden Sie in der Regel eine vollständige Klasse, wenn für Ihre Klasse eine Dokumentation erforderlich ist, um zu beschreiben, wofür sie gedacht ist, oder wenn ein Verhalten vorliegt. Wenn Sie nur temporären Speicher oder eine Gruppierung benötigen, verwenden Sie ein Tupel. Stellen Sie sich eine Situation vor, in der Sie mehr als einen Wert von einer asynchronen Methode zurückgeben möchten. Tuple wurde entwickelt, um dieses Problem zu lösen.

Gusdor
quelle
12

Verwenden Sie eine Klasse

Wenn Ihre Objekte Entitäten sind, die in Ihrer gesamten Anwendung weit verbreitet sind und auch in einem dauerhaften Speicher wie einer relationalen Datenbank (SQL Server, MySQL, SQLite), einer NoSQL-Datenbank oder einem Cache (Redis, Azure DocumentDB) oder sogar einfach gespeichert sind Textdateien oder CSVs.

Also ja, alles, was hartnäckig ist, sollte eine eigene Klasse haben.

Verwenden Sie ein Tupel

Wenn Ihre Objekte nur von kurzer Dauer sind und keine besondere Bedeutung für Ihre Anwendung haben. Wenn Sie beispielsweise schnell ein Koordinatenpaar zurückgeben müssen, ist es besser, Folgendes zu haben:

(double Latitude, double Longitude) getCoordinates()
{
    return (144.93525, -98.356346);
}

als eine separate Klasse definieren

class Coordinates
{
    public double Latitude { get; set; }
    public double Longitude { get; set; }
}

Ein Tupel spart Ihnen Zeit, da Sie newfür eine so einfache Operation keinen Speicher auf dem Heap zuweisen müssen .

Ein anderes Mal, wenn ich Tupel nützlich finde, ist das Ausführen mehrerer mathematischer Operationen an einigen Operanden

(double Result1, double Result2, double Result3) performCalculations(int operand1, int operand 2)

In diesem Fall ist es nicht sinnvoll, eine Klasse zu definieren. Unabhängig von den Berechnungen gehören die Ergebnisse nicht zu einer Klasse. Die Alternative wäre also, outVariablen zu verwenden , aber ich glaube, Tupel sind aussagekräftiger und führen zu einer verbesserten Lesbarkeit.

Dimlucas
quelle
2
Ich würde definieren Coordinatesals structanstelle von class.
Orkhan Alikhanov
6
Koordinaten sind ein gutes Beispiel dafür, dass Sie kein Tupel verwenden, da es häufig genug ist, dass Sie es wahrscheinlich ständig benötigen . Darüber hinaus können Sie überladen ToString, um eine lesbare Ausgabe bereitzustellen (im Übrigen funktioniert der Standard-ToString des Tupels hier bereits gut), und auch überladen Equals/ GetHashCodefür Gleichheitsvergleiche.
Poke
IMHO, dies scheint eine sehr einfühlsame Antwort zu sein, mit einer vagen oop desgin-Richtlinie. Ich glaube nicht, dass oop etwas damit zu tun hat (anonyme Klassen können in beide Kategorien eingeteilt werden). Das Hinzufügen von Persistenz auf Datenbankebene hat auch nichts mit diesen Überlegungen zu tun.
Daniel Dubovski
3

Im Allgemeinen möchten Sie eine Klasse haben, wenn Ihr Objekt an einem anderen Ort verwendet wird oder wenn es ein reales Objekt oder ein Konzept in Ihrer Domäne darstellt. Sie werden wahrscheinlich eine Klasse erstellen, die ein Auto oder ein Autohaus darstellt, keine Tupel.

Andererseits möchten Sie manchmal nur ein paar Objekte von einer Methode zurückgeben. Vielleicht stellen sie nichts Besonderes dar, es ist nur so, dass Sie sie in dieser bestimmten Methode zusammengeben müssen. Selbst wenn sie ein Konzept aus Ihrer Domain darstellen (sagen wir, Sie kehren zurück (Car, Store), das als SaleObjekt dargestellt werden könnte), werden Sie sie manchmal nirgendwo verwenden - Sie verschieben nur Daten. In diesen Fällen ist es in Ordnung, Tupel zu verwenden.

Wenn Sie nun speziell über C # sprechen, sollten Sie noch etwas wissen. Der Tupeltyp von C # 7 ist eigentlich der ValueTuple, der eine Struktur ist. Im Gegensatz zu Klassen, die Referenztypen sind, sind Strukturen Werttypen. Sie können mehr darüber auf msdn lesen . Am wichtigsten ist, dass Sie wissen, dass sie möglicherweise viel kopieren. Seien Sie also vorsichtig.

andre_ss6
quelle
3

Ich denke, das wird eine Frage, die oft gestellt wird. Derzeit gibt es keine "Best Practice" für die Verwendung der neuen Wertetupel gegenüber einer Klasse.

Es lohnt sich jedoch zu lesen, was in früheren Gesprächen über die vorherigen Versionen von Tupeln gegen eine Klasse aufgetaucht ist .

Meiner Meinung nach sollte das Wertetupel nur minimal und mit maximal drei Werten verwendet werden. Ich denke, dies schafft eine gute Balance zwischen "Rückgabe einiger Werte ohne die Notwendigkeit einer Klasse" und "schrecklichem Durcheinander von Werten". Wenn Sie mehr als drei Werte zurückgeben müssen, erstellen Sie eine Klasse.

Ich würde auch niemals ein Tupel verwenden, um von einer öffentlich zugänglichen API zurückzukehren, die Verbraucher verwenden müssen. Verwenden Sie einfach eine Klasse.

Hier ist ein realer Code, den ich verwendet habe:

public async Task<(double temperature, double humidity, string description)> DownloadTodaysForecast()

Sobald ich komplexere Daten zurückgeben möchte, erstelle ich eine Klasse.

user9993
quelle
3

Ich möchte zunächst erwähnen, dass C # bereits Unterstützung für anonyme Typen hatte . Welches sind Referenztypen. Sie hatten also bereits eine geeignete Alternative zum Erstellen einer benannten Klasse.

Ein Vorteil einer benannten Klasse besteht darin, dass sie leichter wiederverwendet werden kann (z. B. wenn Sie denselben Typ an mehreren Stellen benötigen) und dokumentiert. Da der anonyme Typ anonym ist, können Sie nur dann Variablen eingeben, wenn Sie ihn verwenden können. Dies varschränkt die Kontexte ein, in denen anonyme Typen nützlich sind (z. B. können Sie sie nicht als Feldtypen, Rückgabetypen oder Parametertypen verwenden). .

Natürlich können Sie einige der Einschränkungen anonymer Typen mit verwenden System.Tuple. Dies ist auch ein Referenztyp, den Sie explizit verwenden können. Der Nachteil ist, dass es keine benutzerdefinierten Namen für die Mitglieder gibt.


C # 7-Tupel ( ValueTuple) können als ähnlich wie anonyme Typen angesehen werden. Der erste Unterschied besteht darin, dass es sich um Werttypen handelt. Dies bedeutet, dass diese Tupel einen Leistungsvorteil haben, solange sie sich im lokalen Bereich befinden oder sich über den Stapel bewegen (was aufgrund seiner Einschränkung die übliche Verwendung anonymer Typen ist).

Der zweite Unterschied besteht darin, dass die neue Syntax ermöglicht, dass Tupel an mehr Stellen als anonyme Typen angezeigt werden. Wie Sie wissen, haben Sie syntaktischen Zucker, um den Rückgabetyp zu definieren ValueTuple(während Sie anonyme Typen verwenden, die Sie zurückgeben mussten object).

Der dritte Unterschied besteht darin, dass die ValueTuplesofortige Dekonstruktion unterstützt wird. So zitieren Sie die Neuerungen in C # 7.0 :

Eine andere Möglichkeit, Tupel zu konsumieren, besteht darin, sie zu dekonstruieren. Eine dekonstruierende Deklaration ist eine Syntax zum Aufteilen eines Tupels (oder eines anderen Werts) in seine Teile und zum individuellen Zuweisen dieser Teile zu neuen Variablen:

(string first, string middle, string last) = LookupName(id1); // deconstructing declaration
WriteLine($"found {first} {last}.");

Dies können Sie auch mit benutzerdefinierten Typen tun, indem Sie eine Deconstruct-Methode hinzufügen .


Für Zusammenfassung:

  • ValueTuple hat syntaktischen Zucker in C # 7.0, der für die Lesbarkeit berücksichtigt werden sollte.
  • ValueTupleist ein Werttyp. Alle Vor- und Nachteile zwischen der Verwendung von a classund a structgelten.
  • ValueTuplekann explizit verwendet werden (mit oder ohne syntaktischen Zucker), so dass es die Vielseitigkeit hat, System.Tuplewährend benannte Mitglieder erhalten bleiben .
  • ValueTuple unterstützt die Dekonstruktion.

Angesichts der Tatsache, dass es sich bei dem Muss um syntaktischen Zucker handelt, würde ich sagen, dass das stärkere Argument für die Wahl ValueTupledasselbe Argument für die Wahl von a ist struct. Das wäre ideal für kleine, unveränderliche Typen, die meistens auf dem Stapel leben (so dass Sie nicht viel Boxen und Unboxen haben).

Im Vergleich zu ValueTupleeiner vollständigen Struktur würde ich unter Berücksichtigung des syntaktischen Zuckers empfehlen, diese ValueTuplestandardmäßig zu verwenden, es sei denn, Sie benötigen ein explizites Layout oder müssen Methoden hinzufügen.

Ich möchte auch sagen, dass der syntaktische Zucker die Lesbarkeit nicht unbedingt verbessert. Der Hauptgrund ist, dass Sie den Typ nicht benennen und der Name des Typs dem Code eine Bedeutung verleiht. Darüber hinaus können Sie einer structoder einer classErklärung eine Dokumentation hinzufügen , die das Verständnis erleichtert.

Alles in allem gibt die Situation, in der ValueTuplewirklich glänzt, mehrere Werte von einer Methode zurück. In diesem Fall müssen keine neuen outParameter erstellt werden. Und die Dokumentation der ValueTupleverwendeten kann in der Dokumentation der Methode leben. Wenn Sie feststellen, dass Sie etwas anderes mit dem tun müssen ValueTuple(z. B. das Definieren von Erweiterungsmethoden), würde ich empfehlen, stattdessen einen benannten Typ zu erstellen.

Theraot
quelle
2

Tupel sollen mehrere Werte darstellen, beispielsweise wenn eine Methode mehrere Werte zurückgeben möchte. Die Tupelunterstützung in C # 7 verwendet System.ValueTuple<...>Instanzen, um diesen Wertesatz darzustellen. Die Namen dieser Werte sind nur in dem Kontext gültig, in dem sie verwendet werden, und werden nicht erzwungen.

Klassen sollen einen einzelnen Wert mit mehreren Attributen darstellen.

Paulo Morgado
quelle
1

Tupel ist eine großartige Option, wenn Sie mehrere Werte (können unterschiedliche Typen sein) zu einem Objekt kombinieren möchten, ohne eine benutzerdefinierte Klasse zu erstellen. In diesem Fall wäre Tuple eine schnelle und perfekte Option.

Qasim Bataineh
quelle
0

Ich würde es vermeiden, Tupel als öffentliche Methode vom Rückgabetyp zu verwenden. In solchen Fällen würde ich lieber eine Klasse oder Struktur definieren.

Frederik Gheysels
quelle
1
Können Sie erklären, warum?
Fütemire