Unterstützt eine zukünftige Version von .NET Tupel in C #?


Antworten:


73

Ich habe gerade diesen Artikel aus dem MSDN Magazine gelesen: Building Tuple

Hier einige Auszüge:

Mit der kommenden Version 4.0 von Microsoft .NET Framework wird ein neuer Typ namens System.Tuple eingeführt. System.Tuple ist eine Sammlung heterogen typisierter Daten mit fester Größe.    

 

Wie ein Array hat ein Tupel eine feste Größe, die nach seiner Erstellung nicht mehr geändert werden kann. Im Gegensatz zu einem Array kann jedes Element in einem Tupel ein anderer Typ sein, und ein Tupel kann eine starke Typisierung für jedes Element garantieren.

 

Es gibt bereits ein Beispiel für ein Tupel, das im Microsoft .NET Framework im System.Collections.Generic-Namespace schwebt: KeyValuePair. Während KeyValuePair als dasselbe wie Tuple angesehen werden kann, da beide Typen zwei Dinge enthalten, unterscheidet sich KeyValuePair von Tuple, da es eine Beziehung zwischen den beiden gespeicherten Werten hervorruft (und dies aus gutem Grund, da es die Dictionary-Klasse unterstützt ).

Darüber hinaus können Tupel beliebig dimensioniert werden, während KeyValuePair nur zwei Dinge enthält: einen Schlüssel und einen Wert.


Während einige Sprachen wie F # eine spezielle Syntax für Tupel haben, können Sie den neuen allgemeinen Tupeltyp aus jeder Sprache verwenden. Wenn wir uns das erste Beispiel noch einmal ansehen, können wir sehen, dass Tupel in Sprachen ohne Syntax für ein Tupel übermäßig ausführlich sein können, obwohl sie nützlich sind:

class Program {
    static void Main(string[] args) {
        Tuple<string, int> t = new Tuple<string, int>("Hello", 4);
        PrintStringAndInt(t.Item1, t.Item2);
    }
    static void PrintStringAndInt(string s, int i) {
        Console.WriteLine("{0} {1}", s, i);
    }
}

Mit dem Schlüsselwort var aus C # 3.0 können wir die Typensignatur für die Tupelvariable entfernen, wodurch etwas besser lesbarer Code möglich ist.

var t = new Tuple<string, int>("Hello", 4);

Wir haben einer statischen Tupelklasse auch einige Factory-Methoden hinzugefügt, die es einfacher machen, Tupel in einer Sprache zu erstellen, die Typinferenz unterstützt, wie z. B. C #.

var t = Tuple.Create("Hello", 4);

69
#region tuples

    public class Tuple<T>
    {
        public Tuple(T first)
        {
            First = first;
        }

        public T First { get; set; }
    }

    public class Tuple<T, T2> : Tuple<T>
    {
        public Tuple(T first, T2 second)
            : base(first)
        {
            Second = second;
        }

        public T2 Second { get; set; }
    }

    public class Tuple<T, T2, T3> : Tuple<T, T2>
    {
        public Tuple(T first, T2 second, T3 third)
            : base(first, second)
        {
            Third = third;
        }

        public T3 Third { get; set; }
    }

    public class Tuple<T, T2, T3, T4> : Tuple<T, T2, T3>
    {
        public Tuple(T first, T2 second, T3 third, T4 fourth)
            : base(first, second, third)
        {
            Fourth = fourth;
        }

        public T4 Fourth { get; set; }
    }

    #endregion

Und um Erklärungen schöner zu machen:

public static class Tuple
{
    //Allows Tuple.New(1, "2") instead of new Tuple<int, string>(1, "2")
    public static Tuple<T1, T2> New<T1, T2>(T1 t1, T2 t2)
    {
        return new Tuple<T1, T2>(t1, t2);
    }
    //etc...
}

1
Während sich die Frage auf MS bezieht, die es in .NET 4 bereitstellt, ist dies eine gute Möglichkeit, es vorerst zu verwalten. +1
Chris Charabaruk

6
Dieser Vererbungsansatz ist in Ordnung, wenn Sie nicht zwei Tupel für die Gleichheit vergleichen müssen. Ich wollte IEquatable <Tupel <T1, T2 >> usw. in meiner Implementierung implementieren, daher konnte ich keine Vererbung verwenden, da ich nicht wollte, dass ein Tupel <T1, T2> gleich einem Tupel <T1, ist. T2, T3, T4>.
Joel Mueller

3
@Joel, Sie können den dynamischen Typ beider Argumente von Equals überprüfen lassen.
RossFabricant

Wie würden Sie es im Code verwenden? t = neues Tupel (1, 2); t.First und t.Second?
Andriy Drozdyuk

1
Ich sehe, dass Sie Vererbung verwenden - möchten Sie wirklich in der Lage sein, eine Tuple<T1,T2,T3>als zu übergeben Tuple<T1,T2>? Die Antwort ist wahrscheinlich nein.
Callum Rogers

15

In Lokad Shared Libraries (natürlich Open Source ) gibt es eine ordnungsgemäße (nicht schnelle) C # -Tupel- Implementierung , die die folgenden erforderlichen Funktionen enthält:

  • 2-5 unveränderliche Tupelimplementierungen
  • Richtiges DebuggerDisplayAttribute
  • Richtiges Hashing und Gleichheitsprüfungen
  • Helfer zum Generieren von Tupeln aus den bereitgestellten Parametern (Generika werden vom Compiler abgeleitet) und Erweiterungen für sammlungsbasierte Operationen.
  • produktionsgeprüft.

14

Das Implementieren von Tupelklassen oder das Wiederverwenden von F # -Klassen in C # ist nur die halbe Wahrheit - diese geben Ihnen die Möglichkeit, Tupel relativ einfach zu erstellen, aber nicht wirklich den syntaktischen Zucker, der sie in Sprachen wie F # so schön macht.

Zum Beispiel können Sie in F # Pattern Matching verwenden, um beide Teile eines Tupels innerhalb einer Let-Anweisung zu extrahieren, z

let (a, b) = someTupleFunc

Leider wäre es viel weniger elegant, dasselbe mit den F # -Klassen von C # zu tun:

Tuple<int,int> x = someTupleFunc();
int a = x.get_Item1();
int b = x.get_Item2();

Tupel stellen eine leistungsstarke Methode dar, um mehrere Werte aus einem Funktionsaufruf zurückzugeben, ohne dass Sie Ihren Code mit Wegwerfklassen verunreinigen oder auf hässliche ref- oder out-Parameter zurückgreifen müssen. Meiner Meinung nach sind sie jedoch ohne syntaktischen Zucker, um ihre Kreation und ihren Zugang eleganter zu gestalten, von begrenztem Nutzen.


Wie wäre es dann mit anonymen Typen?
Chris Charabaruk

Auf diese Weise können Sie Tuple <int, int> durch var ersetzen, aber Sie haben immer noch drei Codezeilen anstatt einer
Chris Ballard,

Vielleicht sehen wir das in C # 4.0. Syntaktischer Zucker wie int a, b = someTupleFunc (); sollte auf Compilerebene perfekt machbar sein.
Adam Lassek

12

Meiner Meinung nach ist die Funktion für anonyme Typen kein Tupel, sondern ein sehr ähnliches Konstrukt. Die Ausgabe einiger LINQ-Abfragen sind Sammlungen anonymer Typen, die sich wie Tupel verhalten.

Hier ist eine Aussage, die ein typisiertes Tupel erzeugt :-) on the fly:

var p1 = new {a = "A", b = 3};

Siehe: http://www.developer.com/net/csharp/article.php/3589916


5

C # 7 unterstützt Tupel nativ:

var unnamedTuple = ("Peter", 29);
var namedTuple = (Name: "Peter", Age: 29);
(string Name, double Age) typedTuple = ("Peter", 29);

1
Dies funktioniert ab .NET 4.7 sofort , da ältere Versionen von .NET nicht die erforderlichen ValueTupleStrukturen enthalten. Sie können diese jedoch beispielsweise mithilfe eines NuGet-Pakets wie ValueTupleBridge bereitstellen .
tm1

1
MS hat jetzt ein offizielles Nuget-Paket für die erforderliche (n)

3

Meine Open-Source-.NET- Sasa-Bibliothek enthält seit Jahren Tupel (zusammen mit vielen anderen Funktionen wie dem vollständigen MIME-Parsing). Ich benutze es jetzt seit ein paar Jahren im Produktionscode.


3

C # unterstützt einfache Tupel über Generika ganz einfach (gemäß einer früheren Antwort), und mit "Mumble Typing" (eine von vielen möglichen Verbesserungen der C # -Sprache) zur Verbesserung der Typinferenz können sie sehr, sehr leistungsfähig sein.

Für das, was es wert ist, unterstützt F # Tupel von Haus aus, und nachdem ich damit gespielt habe, bin ich mir nicht sicher, ob (anonyme) Tupel viel hinzufügen ... was Sie an Kürze gewinnen, verlieren Sie sehr schnell an Codeklarheit.

Für Code innerhalb einer einzelnen Methode gibt es anonyme Typen. Für Code, der außerhalb einer Methode liegt, halte ich mich an einfache benannte Typen. Wenn ein zukünftiges C # es einfacher macht, diese unveränderlich zu machen (obwohl es immer noch einfach ist, damit zu arbeiten), bin ich natürlich glücklich.


Einer der Vorteile ist beispielsweise TryParse, bei dem Sie 'valid value = double.TryParse ("1.02")' schreiben und mehrere Rückgabewerte ohne umständliche Parameter zuweisen können. Aber im Allgemeinen stimme ich zu, dass rein positionelle Datenstrukturen keine gute Sache sind, um sie weiterzugeben.
Greg Beech

2
Einverstanden - Mehrere Rückgabewerte sind der beste Anwendungsfall für Tupel, und ansonsten gibt es für Tupel im Code nur einen sehr geringen Zweck. Dies sollte jedoch eher eine Konvention als eine Einschränkung sein, wenn Tupel zulässig sind. Was ich für ordentlicher gehalten hätte, wäre, wenn .NET-Sprachen eine Möglichkeit bieten würden, zurückgegebene Objekte in mehrere Werte zu "zerlegen", anstatt den Tupel-Datentyp einzuführen. So etwas wie { j = ErrorCode, h = ResultObj } = SomeFunction()(wo jund hsind Einheimische) wäre viel hilfreicher gewesen als Tupel.
Walt W

2

Hier sind meine Tupel, die von einem Python-Skript automatisch generiert werden. Ich bin also vielleicht etwas über Bord gegangen:

Link zum Subversion-Repository

Sie benötigen einen Benutzernamen / ein Passwort, beide sind Gast

Sie basieren auf Vererbung, werden jedoch Tuple<Int32,String>nicht gleich verglichen, Tuple<Int32,String,Boolean>selbst wenn sie zufällig dieselben Werte für die beiden ersten Mitglieder haben.

Sie implementieren auch GetHashCode und ToString usw. sowie viele kleinere Hilfsmethoden.

Anwendungsbeispiel:

Tuple<Int32, String> t1 = new Tuple<Int32, String>(10, "a");
Tuple<Int32, String, Boolean> t2 = new Tuple<Int32, String, Boolean>(10, "a", true);
if (t1.Equals(t2))
    Console.Out.WriteLine(t1 + " == " + t2);
else
    Console.Out.WriteLine(t1 + " != " + t2);

Wird ausgegeben:

10, a != 10, a, True

Das ist aber noch nicht alles. Wenn es auf Vererbung basiert, könnten Sie wahrscheinlich schreiben Tuple<Int32, String> t1 = new Tuple<Int32, String, Boolean>(10, "a", true);usw. Ich bin mir nicht sicher, ob es ein Szenario gibt, das gewünscht wird.
Nawfal

1

Wenn ich mich richtig erinnere, sind Tupel nur Daten.

Wenn Sie gruppierte Daten möchten, erstellen Sie Klassen, die Eigenschaften enthalten. Wenn Sie so etwas wie das KeyValuePair benötigen, dann ist es da.


3
Genau wie wenn Anweisungen nur Gotos mit einem Anweisungsblock sind. Tupel sind nett, wenn Sie an sie gewöhnt sind und Klassen in bestimmten Fällen unnötig und unhandlich erscheinen ...
Daniel O

Es sei denn, Sie müssen 5 Elemente von einer Funktion zurückgeben. Natürlich können Sie Ihre eigene generische Klasse erstellen, aber die Syntax in C # 7 ist viel sauberer und einfacher.
AustinWBryan

0

Ich wäre überrascht - C # ist eine stark typisierte Sprache, während Tupel für dynamisch typisierte Sprachen geeignet sind. C # hat sich im Laufe der Zeit dynamischer entwickelt, aber das ist syntaktischer Zucker, keine echte Verschiebung der zugrunde liegenden Datentypen.

Wenn Sie zwei Werte in einer Instanz möchten, ist ein KeyValuePair <> ein anständiger Ersatz, wenn auch ungeschickt. Sie können auch eine Struktur oder eine Klasse erstellen, die dasselbe tut und erweiterbar ist.


2
"C # ist dynamischer geworden" - nein, es ist impliziter / gefolgerter geworden; Es ist immer noch eine vollständig statische Sprache. Eine bessere dynamische Unterstützung (für den Konsum von DLR-Klassen) ist jedoch eine sehr wahrscheinliche zukünftige Sprachverbesserung.
Marc Gravell

2
Wie erklären Sie das Vorhandensein von Tupeln in F # oder Haskell oder einer der anderen stark und statisch typisierten Sprachen, die sie dann voll unterstützen ...?
Greg Beech

0

Um diese in einer Hashtabelle oder einem Wörterbuch nützlich zu machen, möchten Sie wahrscheinlich Überladungen für GetHashCode und Equals bereitstellen.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.