Mit der Hinzufügung der Tuple- Klasse in .net 4 habe ich versucht zu entscheiden, ob die Verwendung in meinem Design eine schlechte Wahl ist oder nicht. So wie ich es sehe, kann ein Tupel eine Abkürzung zum Schreiben einer Ergebnisklasse sein (ich bin sicher, dass es auch andere Verwendungszwecke gibt).
Also das:
public class ResultType
{
public string StringValue { get; set; }
public int IntValue { get; set; }
}
public ResultType GetAClassedValue()
{
//..Do Some Stuff
ResultType result = new ResultType { StringValue = "A String", IntValue = 2 };
return result;
}
Ist gleichbedeutend damit:
public Tuple<string, int> GetATupledValue()
{
//...Do Some stuff
Tuple<string, int> result = new Tuple<string, int>("A String", 2);
return result;
}
Beiseite die Möglichkeit so einstellen , dass ich den Punkt von Tupeln bin fehlt, ist das Beispiel mit einem Tuple ein schlechtes Design Wahl? Für mich scheint es weniger Unordnung zu sein, aber nicht so selbstdokumentierend und sauber. Das bedeutet, dass mit dem Typ ResultType
später sehr klar wird, was jeder Teil der Klasse bedeutet, aber Sie müssen zusätzlichen Code pflegen. Mit dem müssen Tuple<string, int>
Sie nachschlagen und herausfinden, was jedes Item
darstellt, aber Sie schreiben und pflegen weniger Code.
Jede Erfahrung, die Sie mit dieser Wahl gemacht haben, wäre sehr dankbar.
quelle
Antworten:
Tupel sind großartig, wenn Sie sowohl das Erstellen als auch das Verwenden steuern. Sie können den Kontext beibehalten, der für das Verständnis der Tupel unerlässlich ist.
Auf einer öffentlichen API sind sie jedoch weniger effektiv. Der Verbraucher (nicht Sie) muss die Dokumentation entweder erraten oder nachschlagen, insbesondere für Dinge wie
Tuple<int, int>
.Ich würde sie für private / interne Mitglieder verwenden, aber Ergebnisklassen für öffentliche / geschützte Mitglieder.
Diese Antwort enthält auch einige Informationen.
quelle
StringOps
(im Grunde eine Klasse von "Erweiterungsmethoden" für JavaString
) eine Methodeparition(Char => Boolean): (String, String)
(nimmt Prädikat auf, gibt ein Tupel mit 2 Zeichenfolgen zurück). Es ist offensichtlich, was die Ausgabe ist (offensichtlich wird eines die Zeichen sein, für die das Prädikat wahr war, und das andere, für das es falsch war, aber es ist nicht klar, welches welches ist). Es ist die Dokumentation, die dies klärt (und der Mustervergleich kann verwendet werden, um bei der Verwendung klar zu machen, welches welches ist).Es gibt in der Tat andere wertvolle Verwendungszwecke für
Tuple<>
- die meisten von ihnen beinhalten das Abstrahieren der Semantik einer bestimmten Gruppe von Typen, die eine ähnliche Struktur aufweisen, und das einfache Behandeln dieser Werte als geordnete Menge von Werten. In allen Fällen besteht ein Vorteil von Tupeln darin, dass sie vermeiden, Ihren Namespace mit Nur-Daten-Klassen zu überladen, die Eigenschaften, aber keine Methoden verfügbar machen.Hier ist ein Beispiel für eine sinnvolle Verwendung für
Tuple<>
:Im obigen Beispiel möchten wir ein Paar von Gegnern darstellen. Ein Tupel ist eine bequeme Möglichkeit, diese Instanzen zu koppeln, ohne eine neue Klasse erstellen zu müssen. Hier ist ein weiteres Beispiel:
Eine Pokerhand kann nur als ein Kartensatz betrachtet werden - und Tupel (kann) eine vernünftige Art sein, dieses Konzept auszudrücken.
Die Rückgabe stark typisierter
Tuple<>
Instanzen als Teil einer öffentlichen API für einen öffentlichen Typ ist selten eine gute Idee. Wie Sie selbst erkennen, müssen die beteiligten Parteien (Bibliotheksautor, Bibliotheksbenutzer) bei Tupeln im Voraus den Zweck und die Interpretation der verwendeten Tupeltypen vereinbaren. Es ist schwierig genug, APIs zu erstellen, die intuitiv und klar sind und deren VerwendungTuple<>
nur die Absicht und das Verhalten der API verdeckt.Anonyme Typen sind ebenfalls eine Art Tupel . Sie sind jedoch stark typisiert und ermöglichen es Ihnen, klare, informative Namen für die zum Typ gehörenden Eigenschaften anzugeben. Anonyme Typen sind jedoch für verschiedene Methoden schwierig zu verwenden. Sie wurden hauptsächlich hinzugefügt, um Technologien wie LINQ zu unterstützen, bei denen Projektionen Typen erzeugen, denen wir normalerweise keine Namen zuweisen möchten. (Ja, ich weiß, dass anonyme Typen mit denselben Typen und benannten Eigenschaften vom Compiler konsolidiert werden.)
Meine Faustregel lautet: Wenn Sie es von Ihrer öffentlichen Schnittstelle zurückgeben, machen Sie es zu einem benannten Typ .
Meine andere Faustregel für die Verwendung von Tupeln lautet: Argumente für Namensmethoden und lokale Variablen vom Typ
Tuple<>
so klar wie möglich - lassen Sie den Namen die Bedeutung der Beziehungen zwischen Elementen des Tupels darstellen. Denken Sie an meinvar opponents = ...
Beispiel.Hier ist ein Beispiel für einen realen Fall, in dem ich vermieden habe
Tuple<>
, einen Nur-Daten-Typ für die Verwendung nur in meiner eigenen Assembly zu deklarieren . Die Situation beinhaltet die Tatsache, dass es bei Verwendung von generischen Wörterbüchern mit anonymen Typen schwierig wird, dieTryGetValue()
Methode zum Suchen von Elementen im Wörterbuch zu verwenden, da für die Methode einout
Parameter erforderlich ist, der nicht benannt werden kann:PS Es gibt eine andere (einfachere) Möglichkeit, die Probleme zu umgehen, die sich aus anonymen Typen in Wörterbüchern ergeben. Verwenden Sie dazu das
var
Schlüsselwort, damit der Compiler den Typ für Sie "ableiten" kann. Hier ist diese Version:quelle
Tuple<>
könnte Sinn machen. Es gibt immer Alternativen ...Tupel können nützlich sein ... aber sie können auch später ein Schmerz sein. Wenn Sie eine Methode haben, die zurückgibt,
Tuple<int,string,string,int>
woher wissen Sie später, was diese Werte sind? Waren sieID, FirstName, LastName, Age
oder waren sieUnitNumber, Street, City, ZipCode
.quelle
Tupel sind aus Sicht eines C # -Programmierers eine ziemlich überwältigende Ergänzung der CLR. Wenn Sie über eine Sammlung von Elementen verfügen, deren Länge variiert, müssen diese beim Kompilieren keine eindeutigen statischen Namen haben.
Wenn Sie jedoch eine Sammlung mit konstanter Länge haben, bedeutet dies, dass die festgelegten Positionen in der Sammlung jeweils eine bestimmte vordefinierte Bedeutung haben. Und es ist immer besser , ihr entsprechende statische Namen in diesem Fall zu geben, anstatt die Bedeutung zu erinnern
Item1
,Item2
usw.Anonyme Klassen in C # bieten bereits eine hervorragende Lösung für die häufigste private Verwendung von Tupeln, und sie geben den Elementen aussagekräftige Namen, sodass sie in diesem Sinne tatsächlich überlegen sind. Das einzige Problem ist, dass sie nicht aus benannten Methoden austreten können. Ich würde es vorziehen, wenn diese Einschränkung aufgehoben würde (möglicherweise nur für private Methoden), als eine spezifische Unterstützung für Tupel in C # zu haben:
quelle
struct var SimpleStruct {int x, int y;}
eine Struktur mit einem Namen definiert wird, der mit einer Guid beginnt, die mit einfachen Strukturen und verknüpft ist hat so etwas wie{System.Int16 x}{System.Int16 y}
angehängt; Jeder Name, der mit dieser GUID beginnt, muss eine Struktur darstellen, die nur diese Elemente und bestimmte spezifizierte Inhalte enthält. Wenn mehrere Definitionen für denselben Namen im Gültigkeitsbereich liegen und übereinstimmen, werden alle als der gleiche Typ betrachtet.ref
Parameter benötigen, ein Delegat nur dann an beide Funktionen übergeben werden kann, wenn eine Person a erstellt und kompiliert delegieren Sie den Typ und geben Sie ihn dem anderen zum Bauen. Es gibt keine Möglichkeit, dass beide Personen angeben können, dass die beiden Typen synonym betrachtet werden sollten.Tuple
? Ich habe gerade 50 Stellen in meiner Codebasis überflogen, an denen ich Tupel verwende, und alle sind entweder Rückgabewerte (normalerweiseyield return
) oder Elemente von Sammlungen, die an anderer Stelle verwendet werden. Daher sollten Sie keine anonymen Klassen verwenden.Ähnlich wie das Schlüsselwort
var
ist es als Annehmlichkeit gedacht - wird aber ebenso leicht missbraucht.Meiner bescheidensten Meinung nach nicht
Tuple
als Rückkehrklasse aussetzen . Verwenden Sie es privat, wenn die Datenstruktur eines Dienstes oder einer Komponente dies erfordert, geben Sie jedoch wohlgeformte bekannte Klassen aus öffentlichen Methoden zurück.quelle
var
können nicht zurückgegeben werden oder existieren außerhalb des deklarierten Bereichs. Es ist jedoch immer noch möglich, es über den beabsichtigten Zweck hinaus zu verwenden und "missbraucht" zu werdenDie Verwendung einer Klasse wie
ResultType
ist klarer. Sie können den Feldern in der Klasse aussagekräftige Namen geben (mit einem Tupel würden sieItem1
und genanntItem2
). Dies ist umso wichtiger, wenn die Typen der beiden Felder gleich sind: Der Name unterscheidet sie klar voneinander.quelle
Wie wäre es mit Tupeln in einem Muster zum Dekorieren, Sortieren und Nichtdekorieren? (Schwartzianische Transformation für die Perl). Hier ist zwar ein erfundenes Beispiel, aber Tupel scheinen ein guter Weg zu sein, um mit solchen Dingen umzugehen:
Jetzt hätte ich ein Objekt [] aus zwei Elementen oder in diesem speziellen Beispiel eine Zeichenfolge [] aus zwei Elementen verwenden können. Der Punkt ist, dass ich alles als zweites Element in einem Tupel hätte verwenden können, das intern verwendet wird und ziemlich einfach zu lesen ist.
quelle
IMO sind diese "Tupel" im Grunde alle anonymen
struct
Typen mit öffentlichem Zugriff und unbenannten Mitgliedern .Der einzige Ort, an dem ich Tupel verwenden würde, ist, wenn Sie einige Daten in einem sehr begrenzten Umfang schnell zusammenfügen müssen. Die Semantik der Daten sollte offensichtlich sein , damit der Code nicht schwer zu lesen ist. Die Verwendung eines Tupels (
int
,int
) für (row, col) erscheint daher sinnvoll. Es fällt mir jedoch schwer, einen Vorteil gegenüber einemstruct
mit namentlich genannten Mitgliedern zu finden (es werden also keine Fehler gemacht und Zeilen / Spalten werden nicht versehentlich ausgetauscht).Wenn Sie Daten an den Anrufer zurücksenden oder Daten von einem Anrufer akzeptieren, sollten Sie a
struct
mit benannten Mitgliedern verwenden.Nehmen Sie ein einfaches Beispiel:
Die Tupelversion
Ich sehe keinen Vorteil darin, Tupel anstelle einer Struktur mit benannten Mitgliedern zu verwenden. Die Verwendung unbenannter Mitglieder ist ein Rückschritt für die Lesbarkeit und Verständlichkeit Ihres Codes.
Tupel scheint mir ein fauler Weg zu sein, um zu vermeiden, ein
struct
mit tatsächlich benannten Mitgliedern zu erstellen . Übermäßiger Gebrauch von Tupel, bei dem Sie wirklich das Gefühl haben, dass Sie / oder jemand anderes, der auf Ihren Code stößt, benannte Mitglieder benötigt, ist A Bad Thing ™, falls ich jemals eines gesehen habe.quelle
Beurteilen Sie mich nicht, ich bin kein Experte, aber mit neuen Tupeln in C # 7.x könnten Sie etwas zurückgeben wie:
Zumindest können Sie es jetzt benennen und Entwickler sehen möglicherweise einige Informationen.
quelle
Das kommt natürlich darauf an! Wie Sie sagten, kann ein Tupel Ihnen Code und Zeit sparen, wenn Sie einige Artikel für den lokalen Verbrauch zusammenfassen möchten. Sie können sie auch verwenden, um allgemeinere Verarbeitungsalgorithmen zu erstellen, als wenn Sie eine konkrete Klasse weitergeben. Ich kann mich nicht erinnern, wie oft ich mir gewünscht habe, etwas anderes als KeyValuePair oder eine DataRow zu haben, um schnell ein Datum von einer Methode zur anderen zu übergeben.
Auf der anderen Seite ist es durchaus möglich, es zu übertreiben und Tupel herumzugeben, bei denen man nur raten kann, was sie enthalten. Wenn Sie ein Tupel für mehrere Klassen verwenden möchten, ist es möglicherweise besser, eine konkrete Klasse zu erstellen.
In Maßen verwendet, können Tupel natürlich zu einem präziseren und lesbareren Code führen. In C ++, STL und Boost finden Sie Beispiele für die Verwendung von Tupeln in anderen Sprachen. Am Ende müssen wir jedoch alle experimentieren, um herauszufinden, wie sie am besten in die .NET-Umgebung passen.
quelle
Tupel sind eine nutzlose Framework-Funktion in .NET 4. Ich denke, mit C # 4.0 wurde eine großartige Gelegenheit verpasst. Ich hätte gerne Tupel mit benannten Mitgliedern gehabt, damit Sie auf die verschiedenen Felder eines Tupels mit Namen anstelle von Wert1 , Wert2 usw. zugreifen können.
Es hätte eine Änderung der Sprache (Syntax) erforderlich gemacht, aber es wäre sehr nützlich gewesen.
quelle
let success, value = MethodThatReturnsATuple()
Ich persönlich würde niemals ein Tupel als Rückgabetyp verwenden, da es keinen Hinweis darauf gibt, was die Werte darstellen. Tupel haben einige wertvolle Verwendungszwecke, da sie im Gegensatz zu Objekten Werttypen sind und somit Gleichheit verstehen. Aus diesem Grund werde ich sie als Wörterbuchschlüssel verwenden, wenn ich einen mehrteiligen Schlüssel benötige, oder als Schlüssel für eine GroupBy-Klausel, wenn ich nach mehreren Variablen gruppieren möchte und keine verschachtelten Gruppierungen möchte (Wer möchte jemals verschachtelte Gruppierungen?). Um das Problem mit extremer Ausführlichkeit zu lösen, können Sie sie mit einer Hilfsmethode erstellen. Denken Sie daran, wenn Sie häufig auf Mitglieder zugreifen (über Item1, Item2 usw.), sollten Sie wahrscheinlich ein anderes Konstrukt verwenden, z. B. eine Struktur oder eine anonyme Klasse.
quelle
Ich habe sowohl Tupel
Tuple
als auch neue TupelValueTuple
in verschiedenen Szenarien verwendet und bin zu folgendem Schluss gekommen: Nicht verwenden .Jedes Mal sind folgende Probleme aufgetreten:
Meiner Meinung nach sind Tupel ein Nachteil und kein Merkmal von C #.
Ich habe etwas ähnliche, aber viel weniger harte Kritik an
Func<>
undAction<>
. Diese sind in vielen Situationen nützlich, insbesondere in einfachenAction
undFunc<type>
Varianten, aber darüber hinaus habe ich festgestellt, dass das Erstellen eines Delegatentyps eleganter, lesbarer und wartbarer ist und Ihnen mehr Funktionen wieref
/out
parameter bietet .quelle
ValueTuple
- und ich mag sie auch nicht. Erstens ist die Benennung nicht stark, sondern eher ein Vorschlag, der sehr leicht durcheinander zu bringen ist, da sie implizit entwederTuple
oderValueTuple
mit derselben Anzahl und denselben Arten von Elementen zugewiesen werden kann. Zweitens sind der Mangel an Vererbung und die Notwendigkeit, die gesamte Definition von Ort zu Ort zu kopieren und einzufügen, immer noch nachteilig.