Esempio pratico in cui Tuple può essere utilizzato in .Net 4.0?


97

Ho visto la tupla introdotta in .Net 4 ma non sono in grado di immaginare dove possa essere utilizzata. Possiamo sempre creare una classe personalizzata o uno Struct.


13
Forse questo è un buon punto per notare che questo argomento è molto antico. Guarda cosa è successo in C # 7 !
maf-soft

Risposte:


83

Questo è il punto: è più conveniente non creare sempre una classe o una struttura personalizzata. È un miglioramento come Actiono Func... puoi creare questo tipo da solo, ma è conveniente che esistano nel framework.


5
Probabilmente vale la pena sottolineare da MSDN : "Tutti i membri statici pubblici di questo tipo sono thread-safe. Non è garantito che tutti i membri dell'istanza siano thread-safe. "
Matt Borja

Bene, forse i progettisti del linguaggio avrebbero dovuto semplificare la creazione di tipi personalizzati al volo, quindi? Quindi potremmo mantenere la stessa sintassi invece di introdurne un'altra?
Thomas Eyde

@ThomasEyde, che è esattamente quello che hanno fatto negli ultimi 15 anni. Ecco come sono stati aggiunti i membri con corpo di espressione e ora le tuple di valore. Classi Record stile spara alle posteriore taglio nel mese di agosto (9 mesi prima di questo commento) per questa versione di C # 7, e sono probabilmente venendo fuori in C # 8. Si noti inoltre che valore tuple offrono l'uguaglianza valore in cui plain-vecchie classi non lo fanno . L'introduzione di tutto questo nel 2002 richiederebbe preveggenza
Panagiotis Kanavos

cosa intendi con convenient not to make a custom class. Intendi creare un'istanza di classe personalizzata utilizzando =new..()?
Alex

75

Con le tuple potresti facilmente implementare un dizionario bidimensionale (o n-dimensionale per quella materia). Ad esempio, potresti utilizzare un dizionario di questo tipo per implementare una mappatura di cambio valuta:

var forex = new Dictionary<Tuple<string, string>, decimal>();
forex.Add(Tuple.Create("USD", "EUR"), 0.74850m); // 1 USD = 0.74850 EUR
forex.Add(Tuple.Create("USD", "GBP"), 0.64128m);
forex.Add(Tuple.Create("EUR", "USD"), 1.33635m);
forex.Add(Tuple.Create("EUR", "GBP"), 0.85677m);
forex.Add(Tuple.Create("GBP", "USD"), 1.55938m);
forex.Add(Tuple.Create("GBP", "EUR"), 1.16717m);
forex.Add(Tuple.Create("USD", "USD"), 1.00000m);
forex.Add(Tuple.Create("EUR", "EUR"), 1.00000m);
forex.Add(Tuple.Create("GBP", "GBP"), 1.00000m);

decimal result;
result = 35.0m * forex[Tuple.Create("USD", "EUR")]; // USD 35.00 = EUR 26.20
result = 35.0m * forex[Tuple.Create("EUR", "GBP")]; // EUR 35.00 = GBP 29.99
result = 35.0m * forex[Tuple.Create("GBP", "USD")]; // GBP 35.00 = USD 54.58

Preferisco usare un Enum per le abbreviazioni del paese. È possibile?
Zack

1
Certo, dovrebbe funzionare senza problemi poiché le enumerazioni sono tipi di valore.
MarioVW

@MarioVW Potrebbe anche essere realizzato utilizzando un array multidimensionale. In che modo la tupla fa la differenza?
Alex

26

C'è un eccellente articolo nella rivista MSDN che parla del mal di pancia e delle considerazioni di progettazione che sono state necessarie per aggiungere Tuple al BCL. La scelta tra un tipo di valore e un tipo di riferimento è particolarmente interessante.

Come chiarisce l'articolo, la forza trainante dietro Tuple è stata la presenza di tanti gruppi all'interno di Microsoft che ne hanno usufruito, il team F # in primo piano. Sebbene non sia menzionata, ritengo che anche la nuova parola chiave "dinamica" in C # (e VB.NET) abbia qualcosa a che fare con essa, le tuple sono molto comuni nei linguaggi dinamici.

Altrimenti non è particolarmente superiore a creare il tuo poco, almeno puoi dare ai membri un nome migliore.


AGGIORNAMENTO: a causa di una grande revisione in C # versione 7, ora ottenendo molto più amore per la sintassi. Annuncio preliminare in questo post del blog .


23

Ecco un piccolo esempio: supponiamo di avere un metodo che deve cercare l'handle e l'indirizzo e-mail di un utente, dato un ID utente. Puoi sempre creare una classe personalizzata che contiene quei dati, o usare un parametro ref / out per quei dati, oppure puoi semplicemente restituire una tupla e avere una bella firma del metodo senza dover creare un nuovo POCO.

public static void Main(string[] args)
{
    int userId = 0;
    Tuple<string, string> userData = GetUserData(userId);
}

public static Tuple<string, string> GetUserData(int userId)
{
    return new Tuple<string, string>("Hello", "World");
}

10
Questo è un buon esempio, tuttavia, non giustifica l'uso di Tuple.
Amitabh

7
Una tupla si adatta bene qui poiché stai restituendo valori distinti; ma una tupla brilla di più quando restituisci più valori di diversi tipi .
Mark Rushakoff

21
Un altro buon esempio potrebbe essere int.TryParse, in quanto potresti eliminare il parametro di output e utilizzare invece una tupla. Quindi potresti avere Tuple<bool, T> TryParse<T>(string input)e invece di dover usare un parametro di output, ottenere entrambi i valori in una tupla.
Tejs

3
in effetti, questo è esattamente ciò che accade quando chiami un metodo TryParse da F #.
Joel Mueller

23

Ho usato una tupla per risolvere il problema 11 del Progetto Eulero :

class Grid
{
    public static int[,] Cells = { { 08, 02, 22, // whole grid omitted

    public static IEnumerable<Tuple<int, int, int, int>> ToList()
    {
        // code converts grid to enumeration every possible set of 4 per rules
        // code omitted
    }
}

Ora posso risolvere l'intero problema con:

class Program
{
    static void Main(string[] args)
    {
        int product = Grid.ToList().Max(t => t.Item1 * t.Item2 * t.Item3 * t.Item4);
        Console.WriteLine("Maximum product is {0}", product);
    }
}

Avrei potuto usare un tipo personalizzato per questo, ma sarebbe stato esattamente come Tuple .


16

La sintassi delle tuple di C # è ridicolmente voluminosa, quindi le tuple sono difficili da dichiarare. E non ha la corrispondenza del modello, quindi sono anche dolorosi da usare.

Ma a volte, vuoi solo un raggruppamento ad hoc di oggetti senza creare una classe per esso. Ad esempio, diciamo che volevo aggregare un elenco, ma volevo due valori invece di uno:

// sum and sum of squares at the same time
var x =
    Enumerable.Range(1, 100)
    .Aggregate((acc, x) => Tuple.Create(acc.Item1 + x, acc.Item2 + x * x));

Invece di combinare una raccolta di valori in un unico risultato, espandiamo un singolo risultato in una raccolta di valori. Il modo più semplice per scrivere questa funzione è:

static IEnumerable<T> Unfold<T, State>(State seed, Func<State, Tuple<T, State>> f)
{
    Tuple<T, State> res;
    while ((res = f(seed)) != null)
    {
        yield return res.Item1;
        seed = res.Item2;
    }
}

fconverte uno stato in una tupla. Restituiamo il primo valore dalla tupla e impostiamo il nostro nuovo stato sul secondo valore. Questo ci consente di mantenere lo stato durante il calcolo.

Lo usi come tale:

// return 0, 2, 3, 6, 8
var evens =
    Unfold(0, state => state < 10 ? Tuple.Create(state, state + 2) : null)
    .ToList();

// returns 0, 1, 1, 2, 3, 5, 8, 13, 21, 34
var fibs =
    Unfold(Tuple.Create(0, 1), state => Tuple.Create(state.Item1, Tuple.Create(state.Item2, state.Item1 + state.Item2)))
    .Take(10).ToList();

evensè abbastanza semplice, ma fibsè un po 'più intelligente. In staterealtà è una tupla che contiene rispettivamente fib (n-2) e fib (n-1).


4
+1 Tuple.Create è una comoda scorciatoia pernew Tuple<Guid,string,...>
AaronLS

@Juliet Qual è l'uso della parola chiave State
irfandar

7

Non mi piace l'abuso di loro, dal momento che producono codice che non si spiega da solo, ma sono fantastici per implementare chiavi composte al volo, poiché implementano IStructuralEquatable e IStructuralComparable (da usare sia per la ricerca che per l'ordinamento scopi).

E combinano internamente tutti i codici hash dei loro articoli; per esempio, ecco GetHashCode di Tuple (preso da ILSpy):

    int IStructuralEquatable.GetHashCode(IEqualityComparer comparer)
    {
        return Tuple.CombineHashCodes(comparer.GetHashCode(this.m_Item1), comparer.GetHashCode(this.m_Item2), comparer.GetHashCode(this.m_Item3));
    }

7

Le tuple sono ottime per eseguire più operazioni di I / O asincrono alla volta e restituire tutti i valori insieme. Ecco gli esempi di come farlo con e senza Tuple. Le tuple possono effettivamente rendere il tuo codice più chiaro!

Senza (nasty nesting!):

Task.Factory.StartNew(() => data.RetrieveServerNames())
    .ContinueWith(antecedent1 =>
        {
            if (!antecedent1.IsFaulted)
            {
                ServerNames = KeepExistingFilter(ServerNames, antecedent1.Result);
                Task.Factory.StartNew(() => data.RetrieveLogNames())
                    .ContinueWith(antecedent2 =>
                        {
                            if (antecedent2.IsFaulted)
                            {
                                LogNames = KeepExistingFilter(LogNames, antecedent2.Result);
                                Task.Factory.StartNew(() => data.RetrieveEntryTypes())
                                    .ContinueWith(antecedent3 =>
                                        {
                                            if (!antecedent3.IsFaulted)
                                            {
                                                EntryTypes = KeepExistingFilter(EntryTypes, antecedent3.Result);
                                            }
                                        });
                            }
                        });
            }
        });

Con Tuple

Task.Factory.StartNew(() =>
    {
        List<string> serverNames = data.RetrieveServerNames();
        List<string> logNames = data.RetrieveLogNames();
        List<string> entryTypes = data.RetrieveEntryTypes();
        return Tuple.Create(serverNames, logNames, entryTypes);
    }).ContinueWith(antecedent =>
        {
            if (!antecedent.IsFaulted)
            {
                ServerNames = KeepExistingFilter(ServerNames, antecedent.Result.Item1);
                LogNames = KeepExistingFilter(LogNames, antecedent.Result.Item2);
                EntryTypes = KeepExistingFilter(EntryTypes, antecedent.Result.Item3);
            }
        });

Se si sta utilizzando una funzione anonima con un tipo implicito in ogni caso , allora non stanno facendo il codice meno chiaro utilizzando la tupla. Risintonizzare una tupla da un metodo? Usalo con parsimonia quando la chiarezza del codice è fondamentale, a mio modesto parere. So che è difficile resistere alla programmazione funzionale in C #, ma dobbiamo considerare tutti quei goffi programmatori C # "orientati agli oggetti".


5

Le tuple sono ampiamente utilizzate nei linguaggi funzionali che possono fare più cose con loro, ora F # è un linguaggio .net "ufficiale" con cui potresti voler interagire da C # e passarle tra il codice scritto in due lingue.


Le tuple sono anche tipi incorporati per alcuni linguaggi di scripting popolari come Python e Ruby (che hanno anche implementazioni .Net per l'interoperabilità ... IronPython / Ruby).
MutantNinjaCodeMonkey

5

Tendo ad evitare Tupleper la maggior parte degli scenari poiché danneggia la leggibilità. Tuttavia, Tupleè utile quando è necessario raggruppare dati non correlati.

Ad esempio, supponi di avere un elenco di auto e le città in cui sono state acquistate:

Mercedes, Seattle
Mustang, Denver
Mercedes, Seattle
Porsche, Seattle
Tesla, Seattle
Mercedes, Seattle

Vuoi aggregare i conteggi per ogni auto per città:

Mercedes, Seattle [3]
Mustang, Denver [1]
Porsche, Seattle [1]
Tesla, Seattle [1]

Per fare ciò, crei un file Dictionary. Hai alcune opzioni:

  1. Crea un file Dictionary<string, Dictionary<string, int>>.
  2. Crea un file Dictionary<CarAndCity, int>.
  3. Crea un file Dictionary<Tuple<string, string>, int>.

La leggibilità si perde con la prima opzione. Ti richiederà di scrivere molto più codice.

La seconda opzione funziona ed è succinta, ma auto e città non sono realmente correlate e probabilmente non appartengono a una classe insieme.

La terza opzione è succinta e pulita. È un buon uso di Tuple.


4

Alcuni esempi fuori dalla mia testa:

  • Una posizione X e Y (e Z se vuoi)
  • a Larghezza e altezza
  • Qualunque cosa misurata nel tempo

Ad esempio, non vorresti includere System.Drawing in un'applicazione web solo per usare Point / PointF e Size / SizeF.


2
Tupleè utile in situazioni critiche Pointo SomeVectorpuò essere utile quando si eseguono operazioni grafiche. Evidenzia due valori molto legati tra loro. Ho spesso visto proprietà denominate StartTime ed EndTime durante la lettura del codice, ma anche meglio di una data e una durata, una tupla ti obbliga a considerare entrambi i valori ogni volta che operi in quest'area della tua logica aziendale. O restituendo "ehi, i dati sono cambiati (bool), ecco i dati (altro tipo)" in modo pesante invece di passare attraverso associazioni intensive.
Léon Pelletier

3

Dovresti stare molto attento con l'uso Tuplee probabilmente pensarci due volte prima di farlo. Dalla mia precedente esperienza ho scoperto che l'utilizzo Tuplerende il codice molto difficile da leggere e supportare in futuro. Qualche tempo fa, ho dovuto correggere del codice in cui le tuple venivano utilizzate quasi ovunque. Invece di pensare a modelli di oggetti appropriati, hanno usato solo tuple. Quello era un incubo ... a volte volevo uccidere il tizio che ha scritto il codice ...

Non voglio dire che non dovresti usare Tupleed è malvagio o qualcosa del genere e sono sicuro al cento per cento che ci sono alcuni compiti in cui Tupleè il miglior candidato da utilizzare, ma probabilmente dovresti ripensarci, ne hai davvero bisogno ?


1

L'uso migliore per le tuple che ho trovato è quando è necessario restituire più di un tipo di oggetto da un metodo, sai quali tipi di oggetto e numero saranno e non è un lungo elenco.

Altre semplici alternative sarebbero l'utilizzo di un parametro "out"

private string MyMethod(out object)

o fare un dizionario

Dictionary<objectType1, objectType2>

L'uso di una tupla, tuttavia, evita di creare l'oggetto "out" o di dover essenzialmente cercare la voce nel dizionario;


1

Ho appena trovato la soluzione di uno dei miei problemi in Tuple. È come dichiarare una classe nell'ambito di un metodo, ma con una dichiarazione pigra dei nomi dei campi. Si opera con raccolte di tuple, le sue singole istanze e quindi si crea una raccolta di tipo anonimo con i nomi dei campi richiesti, in base alla propria tupla. Questo ti evita di creare la nuova classe per questo scopo.

Il compito è scrivere una risposta JSON da LINQ senza classi aggiuntive:

 //I select some roles from my ORM my with subrequest and save results to Tuple list
 var rolesWithUsers = (from role in roles
                       select new Tuple<string, int, int>(
                         role.RoleName, 
                         role.RoleId, 
                         usersInRoles.Where(ur => ur.RoleId == role.RoleId).Count()
                      ));

 //Then I add some new element required element to this collection
 var tempResult = rolesWithUsers.ToList();
 tempResult.Add(new Tuple<string, int, int>(
                        "Empty", 
                         -1,
                         emptyRoleUsers.Count()
                      ));

 //And create a new anonimous class collection, based on my Tuple list
 tempResult.Select(item => new
            {
                GroupName = item.Item1,
                GroupId = item.Item2,
                Count = item.Item3
            });


 //And return it in JSON
 return new JavaScriptSerializer().Serialize(rolesWithUsers);

Ovviamente potremmo farlo dichiarando una nuova classe per i miei gruppi, ma l'idea di creare raccolte così anonime senza dichiarare nuove classi.


1

Ebbene, nel mio caso, ho dovuto usare una tupla quando ho scoperto che non possiamo usare il parametro out in un metodo asincrono. Leggi qui . Avevo anche bisogno di un diverso tipo di reso. Quindi ho usato una tupla invece come tipo di ritorno e ho contrassegnato il metodo come asincrono.

Codice di esempio di seguito.

...
...
// calling code.
var userDetails = await GetUserDetails(userId);
Console.WriteLine("Username : {0}", userDetails.Item1);
Console.WriteLine("User Region Id : {0}", userDetails.Item2);
...
...

private async Tuple<string,int> GetUserDetails(int userId)
{
    return new Tuple<string,int>("Amogh",105);
    // Note that I can also use the existing helper method (Tuple.Create).
}

Maggiori informazioni su Tuple qui . Spero che questo ti aiuti.


Presumo che tu non voglia restituire un tipo anonimo o un array (o contenuti dinamici)
ozzy432836

0

Cambiare le forme degli oggetti quando è necessario inviarli attraverso il cavo o passare a diversi livelli di applicazione e più oggetti vengono uniti in uno:

Esempio:

var customerDetails = new Tuple<Customer, List<Address>>(mainCustomer, new List<Address> {mainCustomerAddress}).ToCustomerDetails();

ExtensionMethod:

public static CustomerDetails ToCustomerDetails(this Tuple<Website.Customer, List<Website.Address>> customerAndAddress)
    {
        var mainAddress = customerAndAddress.Item2 != null ? customerAndAddress.Item2.SingleOrDefault(o => o.Type == "Main") : null;
        var customerDetails = new CustomerDetails
        {
            FirstName = customerAndAddress.Item1.Name,
            LastName = customerAndAddress.Item1.Surname,
            Title = customerAndAddress.Item1.Title,
            Dob = customerAndAddress.Item1.Dob,
            EmailAddress = customerAndAddress.Item1.Email,
            Gender = customerAndAddress.Item1.Gender,
            PrimaryPhoneNo = string.Format("{0}", customerAndAddress.Item1.Phone)
        };

        if (mainAddress != null)
        {
            customerDetails.AddressLine1 =
                !string.IsNullOrWhiteSpace(mainAddress.HouseName)
                    ? mainAddress.HouseName
                    : mainAddress.HouseNumber;
            customerDetails.AddressLine2 =
                !string.IsNullOrWhiteSpace(mainAddress.Street)
                    ? mainAddress.Street
                    : null;
            customerDetails.AddressLine3 =
                !string.IsNullOrWhiteSpace(mainAddress.Town) ? mainAddress.Town : null;
            customerDetails.AddressLine4 =
                !string.IsNullOrWhiteSpace(mainAddress.County)
                    ? mainAddress.County
                    : null;
            customerDetails.PostCode = mainAddress.PostCode;
        }
...
        return customerDetails;
    }

0

Un parametro out è ottimo quando ci sono solo pochi valori che devono essere restituiti, ma quando inizi a incontrare 4, 5, 6 o più valori che devono essere restituiti, può diventare ingombrante. Un'altra opzione per restituire più valori consiste nel creare e restituire una classe / struttura definita dall'utente o utilizzare una tupla per impacchettare tutti i valori che devono essere restituiti da un metodo.

La prima opzione, utilizzando una classe / struttura per restituire i valori, è semplice. Basta creare il tipo (in questo esempio è una struttura) in questo modo:

public struct Dimensions
{
public int Height;
public int Width;
public int Depth;
}

La seconda opzione, utilizzando una tupla, è una soluzione ancora più elegante rispetto all'utilizzo di un oggetto definito dall'utente. È possibile creare una tupla per contenere un numero qualsiasi di valori di diversi tipi. Inoltre, i dati archiviati nella tupla non sono modificabili; una volta aggiunti i dati alla tupla tramite il costruttore o il metodo Create statico, tali dati non possono essere modificati. Le tuple possono accettare fino a otto valori separati. Se è necessario restituire più di otto valori, sarà necessario utilizzare la classe speciale Tuple: Classe Tuple Quando si crea una tupla con più di otto valori, non è possibile utilizzare il metodo Create statico: è invece necessario utilizzare il costruttore della classe. Ecco come creeresti una tupla di 10 valori interi:

var values = new Tuple<int, int, int, int, int, int, int, Tuple<int, int, int>> (
1, 2, 3, 4, 5, 6, 7, new Tuple<int, int, int> (8, 9, 10));

Ovviamente, puoi continuare ad aggiungere più tuple alla fine di ogni tupla incorporata, creando qualsiasi dimensione di tupla di cui hai bisogno.


0

Solo per la prototipazione: le tuple non hanno significato. È comodo usarli ma è solo una scorciatoia! Per i prototipi - bene. Assicurati solo di eliminare questo codice più tardi.

È facile da scrivere, difficile da leggere. Non ha vantaggi visibili rispetto a classi, classi interne, classi anonime ecc.


0

Bene, ho provato 3 modi per risolvere lo stesso problema in C # 7 e ho trovato un caso d'uso per le tuple.

Lavorare con dati dinamici nei progetti web a volte può essere un problema durante la mappatura, ecc.

Mi piace il modo in cui la tupla è stata mappata automaticamente su item1, item2, itemN che mi sembra più robusto rispetto all'utilizzo di indici di array in cui potresti rimanere intrappolato su un elemento fuori indice o utilizzare il tipo anonimo in cui potresti sbagliare il nome di una proprietà.

Sembra che un DTO sia stato creato gratuitamente semplicemente usando una Tupla e posso accedere a tutte le proprietà usando itemN che sembra più una digitazione statica senza dover creare un DTO separato per quello scopo.

using System;

namespace Playground
{
    class Program
    {
        static void Main(string[] args)
        {
            var tuple = GetTuple();
            Console.WriteLine(tuple.Item1);
            Console.WriteLine(tuple.Item2);
            Console.WriteLine(tuple.Item3);
            Console.WriteLine(tuple);

            Console.WriteLine("---");

            var dyn = GetDynamic();
            Console.WriteLine(dyn.First);
            Console.WriteLine(dyn.Last);
            Console.WriteLine(dyn.Age);
            Console.WriteLine(dyn);

            Console.WriteLine("---");

            var arr = GetArray();
            Console.WriteLine(arr[0]);
            Console.WriteLine(arr[1]);
            Console.WriteLine(arr[2]);
            Console.WriteLine(arr);

            Console.Read();

            (string, string, int) GetTuple()
            {
                return ("John", "Connor", 1);
            }

            dynamic GetDynamic()
            {
                return new { First = "John", Last = "Connor", Age = 1 };
            }

            dynamic[] GetArray()
            {
                return new dynamic[] { "John", "Connor", 1 };
            }
        }
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.