Denominazione migliore nelle classi Tuple di "Item1", "Item2"


204

C'è un modo per usare una classe Tuple, ma fornire i nomi degli oggetti in essa contenuti?

Per esempio:

public Tuple<int, int, int int> GetOrderRelatedIds()

Ciò restituisce gli ID per OrderGroupId, OrderTypeId, OrderSubTypeId e OrderRequirementId.

Sarebbe bello far sapere agli utenti del mio metodo quale è quale. (Quando si chiama il metodo, i risultati sono result.Item1, result.Item2, result.Item3, result.Item4. Non è chiaro quale sia quale.)

(So ​​che potrei semplicemente creare una classe per contenere tutti questi ID, ma questi ID hanno già le loro classi in cui vivono e creare una classe per il valore restituito di questo metodo sembra sciocco.)


1
Dovrai fare il tuo - Tupleè molto generico, quindi è tutto quello che ottieni
BrokenGlass,

NO non puoi fare così, vedi questo link per maggiori informazioni msdn.microsoft.com/en-us/vcsharp/ee957397
Enigma State

1
Mi permetto di dire che l'uso di una Tupla come tipo di dati pubblico per il tuo API potrebbe non essere raccomandato. In genere uso Tuple per cose interne di breve durata non come valore di ritorno di un'API.
Mike Burdick,


4
È nella lista di lavoro di C # 7 Vedi github.com/dotnet/roslyn/issues/347
Philip Ding

Risposte:


277

In C # 7.0 (Visual Studio 2017) c'è una nuova costruzione per farlo:

(string first, string middle, string last) LookupName(long id)

68
La sintassi è List<(int first, int second)>. Ho dovuto scaricare il pacchetto System.ValueTuple da NuGet per farlo funzionare in Visual Studio 2017.
Matt Davis

14
Per creare il valorereturn (first: first, middle: middle, last: last);
fiat

4
o solo: return (first, middle, last);in .NET 4.7.1 (non sicuro per 4.7.0)
watbywbarif il

1
per usarlo dovrai aggiungere il pacchetto nuget System.ValueTuple
Alex G

11
Va notato che C # 7 ValueTuple, sebbene generalmente ottimo, è un tipo di valore mutevole (struct), mentre Tupleè un tipo di riferimento immutabile (class). Per quanto ne so, non c'è modo di ottenere un tipo di riferimento Tuplecon nomi di elementi descrittivi.
dx_over_dt,

51

Fino a C # 7.0, non c'era modo di fare questo a corto di definire il proprio tipo.


14
Non riesco a credere che questa risposta sia accettata con un punteggio di 40. Avresti potuto almeno dimostrare come una classe con un costruttore appropriato possa sostituirla.
bytecode77,

1
@ bytecode77 Beh, abbastanza presto questa risposta sarà sbagliata: github.com/dotnet/roslyn/issues/347
MarkPflug

Ho visto queste proposte di festival linguistici. Ma fino ad ora, una classe è l'unica soluzione adeguata per tipi di dati più complessi. Perché dovresti usare forzatamente le Tuple, non importa quale (vedi altre risposte)
bytecode77

3
Dopo il rilascio di C # 7, sarà possibile farlo: msdn.microsoft.com/en-us/magazine/mt595758.aspx
Burak Karakuş

11
Q 'ha c # 4 come tag, quindi sebbene questa risposta sia breve è comunque corretta.
Steve Drake,

33

Ecco una versione eccessivamente complicata di ciò che stai chiedendo:

class MyTuple : Tuple<int, int>
{
    public MyTuple(int one, int two)
        :base(one, two)
    {

    }

    public int OrderGroupId { get{ return this.Item1; } }
    public int OrderTypeId { get{ return this.Item2; } }

}

Perché non fare semplicemente una lezione?


2
strutt sarebbe meglio in questo caso invece che in classe?
deathrace,

5
Il leggero vantaggio che vedo di questo è che implementa automaticamente l'operatore uguale, controllando che 2 istanze siano uguali se gli elementi sono tutti uguali.
JSoet,

8
Un altro svantaggio di questo approccio è che Item1 e Item2 sono ancora proprietà pubbliche su MyTuple
RJFalconer,

3
@deathrace Le stesse tuple sono classi, quindi se vuoi ereditare direttamente da Tuple<T, T2>te non puoi essere una struttura.
Chakrava,

3
Potrei sbagliarmi, ma uso principalmente la tupla dove voglio restituire un oggetto ma non voglio definire una classe specifica ..
Jay

12

Con .net 4 potresti forse guardare ExpandoObject, tuttavia, non usarlo per questo semplice caso poiché quelli che sarebbero stati errori di compilazione diventano errori di runtime.

class Program
{
    static void Main(string[] args)
    {
        dynamic employee, manager;

        employee = new ExpandoObject();
        employee.Name = "John Smith";
        employee.Age = 33;

        manager = new ExpandoObject();
        manager.Name = "Allison Brown";
        manager.Age = 42;
        manager.TeamSize = 10;

        WritePerson(manager);
        WritePerson(employee);
    }
    private static void WritePerson(dynamic person)
    {
        Console.WriteLine("{0} is {1} years old.",
                          person.Name, person.Age);
        // The following statement causes an exception
        // if you pass the employee object.
        // Console.WriteLine("Manages {0} people", person.TeamSize);
    }
}
// This code example produces the following output:
// John Smith is 33 years old.
// Allison Brown is 42 years old.

Qualcos'altro degno di nota è un tipo anonimo all'interno di un metodo , ma è necessario creare una classe se si desidera restituirlo.

var MyStuff = new
    {
        PropertyName1 = 10,
        PropertyName2 = "string data",
        PropertyName3 = new ComplexType()
    };

10

Riproduzione della mia risposta da questo post in quanto si adatta meglio qui.

Avvio di C # 7.0, ora è possibile dare un nome alle proprietà tuple, che in precedenza utilizzati per impostazione predefinita ai nomi predefiniti come Item1, Item2e così via.

Denominazione delle proprietà di Tuple Literals :

var myDetails = (MyName: "RBT_Yoga", MyAge: 22, MyFavoriteFood: "Dosa");
Console.WriteLine($"Name - {myDetails.MyName}, Age - {myDetails.MyAge}, Passion - {myDetails.MyFavoriteFood}");

L'output su console:

Nome - RBT_Yoga, Età - 22, Passione - Dosa

Tupla di ritorno (con proprietà denominate) da un metodo :

static void Main(string[] args)
{
    var empInfo = GetEmpInfo();
    Console.WriteLine($"Employee Details: {empInfo.firstName}, {empInfo.lastName}, {empInfo.computerName}, {empInfo.Salary}");
}

static (string firstName, string lastName, string computerName, int Salary) GetEmpInfo()
{
    //This is hardcoded just for the demonstration. Ideally this data might be coming from some DB or web service call
    return ("Rasik", "Bihari", "Rasik-PC", 1000);
}

L'output su console:

Dettagli dei dipendenti: Rasik, Bihari, Rasik-PC, 1000

Creazione di un elenco di Tuple con proprietà denominate

var tupleList = new List<(int Index, string Name)>
{
    (1, "cow"),
    (5, "chickens"),
    (1, "airplane")
};

foreach (var tuple in tupleList)
    Console.WriteLine($"{tuple.Index} - {tuple.Name}");

Uscita su console:

1 - mucca 5 - polli 1 - aereo

Spero di aver coperto tutto. Nel caso, c'è qualcosa che mi è sfuggito, ti prego di darmi un feedback nei commenti.

Nota : i miei frammenti di codice utilizzano la funzione di interpolazione delle stringhe di C # v7, come dettagliato qui .


3

MichaelMocko La risposta è fantastica,

ma voglio aggiungere alcune cose che ho dovuto capire

(string first, string middle, string last) LookupName(long id)

sopra la riga ti darà l' errore di compilazione se stai usando .net framework <4.7

Quindi, se hai un progetto che utilizza .net framework <4.7 e vuoi comunque usare ValueTuple di workAround installerebbe questo pacchetto nuget



2

Se i tipi dei tuoi articoli sono tutti diversi, ecco una classe che ho creato per ottenerli in modo più intuitivo.

L'uso di questa classe:

var t = TypedTuple.Create("hello", 1, new MyClass());
var s = t.Get<string>();
var i = t.Get<int>();
var c = t.Get<MyClass>();

Codice sorgente:

public static class TypedTuple
{
    public static TypedTuple<T1> Create<T1>(T1 t1)
    {
        return new TypedTuple<T1>(t1);
    }

    public static TypedTuple<T1, T2> Create<T1, T2>(T1 t1, T2 t2)
    {
        return new TypedTuple<T1, T2>(t1, t2);
    }

    public static TypedTuple<T1, T2, T3> Create<T1, T2, T3>(T1 t1, T2 t2, T3 t3)
    {
        return new TypedTuple<T1, T2, T3>(t1, t2, t3);
    }

    public static TypedTuple<T1, T2, T3, T4> Create<T1, T2, T3, T4>(T1 t1, T2 t2, T3 t3, T4 t4)
    {
        return new TypedTuple<T1, T2, T3, T4>(t1, t2, t3, t4);
    }

    public static TypedTuple<T1, T2, T3, T4, T5> Create<T1, T2, T3, T4, T5>(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5)
    {
        return new TypedTuple<T1, T2, T3, T4, T5>(t1, t2, t3, t4, t5);
    }

    public static TypedTuple<T1, T2, T3, T4, T5, T6> Create<T1, T2, T3, T4, T5, T6>(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6)
    {
        return new TypedTuple<T1, T2, T3, T4, T5, T6>(t1, t2, t3, t4, t5, t6);
    }

    public static TypedTuple<T1, T2, T3, T4, T5, T6, T7> Create<T1, T2, T3, T4, T5, T6, T7>(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7)
    {
        return new TypedTuple<T1, T2, T3, T4, T5, T6, T7>(t1, t2, t3, t4, t5, t6, t7);
    }

    public static TypedTuple<T1, T2, T3, T4, T5, T6, T7, T8> Create<T1, T2, T3, T4, T5, T6, T7, T8>(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8)
    {
        return new TypedTuple<T1, T2, T3, T4, T5, T6, T7, T8>(t1, t2, t3, t4, t5, t6, t7, t8);
    }

}

public class TypedTuple<T>
{
    protected Dictionary<Type, object> items = new Dictionary<Type, object>();

    public TypedTuple(T item1)
    {
        Item1 = item1;
    }

    public TSource Get<TSource>()
    {
        object value;
        if (this.items.TryGetValue(typeof(TSource), out value))
        {
            return (TSource)value;
        }
        else
            return default(TSource);
    }

    private T item1;
    public T Item1 { get { return this.item1; } set { this.item1 = value; this.items[typeof(T)] = value; } }
}

public class TypedTuple<T1, T2> : TypedTuple<T1>
{
    public TypedTuple(T1 item1, T2 item2)
        : base(item1)
    {
        Item2 = item2;
    }

    private T2 item2;
    public T2 Item2 { get { return this.item2; } set { this.item2 = value; this.items[typeof(T2)] = value; } }
}

public class TypedTuple<T1, T2, T3> : TypedTuple<T1, T2>
{
    public TypedTuple(T1 item1, T2 item2, T3 item3)
        : base(item1, item2)
    {
        Item3 = item3;
    }

    private T3 item3;
    public T3 Item3 { get { return this.item3; } set { this.item3 = value; this.items[typeof(T3)] = value; } }
}

public class TypedTuple<T1, T2, T3, T4> : TypedTuple<T1, T2, T3>
{
    public TypedTuple(T1 item1, T2 item2, T3 item3, T4 item4)
        : base(item1, item2, item3)
    {
        Item4 = item4;
    }

    private T4 item4;
    public T4 Item4 { get { return this.item4; } set { this.item4 = value; this.items[typeof(T4)] = value; } }
}

public class TypedTuple<T1, T2, T3, T4, T5> : TypedTuple<T1, T2, T3, T4>
{
    public TypedTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5)
        : base(item1, item2, item3, item4)
    {
        Item5 = item5;
    }

    private T5 item5;
    public T5 Item5 { get { return this.item5; } set { this.item5 = value; this.items[typeof(T5)] = value; } }
}

public class TypedTuple<T1, T2, T3, T4, T5, T6> : TypedTuple<T1, T2, T3, T4, T5>
{
    public TypedTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6)
        : base(item1, item2, item3, item4, item5)
    {
        Item6 = item6;
    }

    private T6 item6;
    public T6 Item6 { get { return this.item6; } set { this.item6 = value; this.items[typeof(T6)] = value; } }
}

public class TypedTuple<T1, T2, T3, T4, T5, T6, T7> : TypedTuple<T1, T2, T3, T4, T5, T6>
{
    public TypedTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7)
        : base(item1, item2, item3, item4, item5, item6)
    {
        Item7 = item7;
    }

    private T7 item7;
    public T7 Item7 { get { return this.item7; } set { this.item7 = value; this.items[typeof(T7)] = value; } }
}

public class TypedTuple<T1, T2, T3, T4, T5, T6, T7, T8> : TypedTuple<T1, T2, T3, T4, T5, T6, T7>
{
    public TypedTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7, T8 item8)
        : base(item1, item2, item3, item4, item5, item6, item7)
    {
        Item8 = item8;
    }

    private T8 item8;
    public T8 Item8 { get { return this.item8; } set { this.item8 = value; this.items[typeof(T8)] = value; } }
}

4
Sembra un sacco di lavoro per un profitto minimo o nullo. Ha una limitazione non intuitiva (nessun tipo duplicato), e trovo l'idea di recuperare un valore solo dal suo tipo incredibilmente non intuitivo e non riesco a pensare a un caso d'uso pratico per esso. Ciò equivale a creare una tabella di dati per i dipendenti, quindi a decidere di recuperare i dipendenti con il loro nome (al contrario di una chiave univoca) e successivamente richiedere a tutti i dipendenti di avere nomi diversi. Questa non è una soluzione a un problema, sta usando una soluzione a costo di creare un problema aggiuntivo.
Flater

E possa Dio avere misericordia della tua anima.
Jamie M.,

1

Questo è molto fastidioso e mi aspetto che le future versioni di C # rispondano a questa esigenza. Trovo che la soluzione più semplice sia utilizzare un diverso tipo di struttura di dati o rinominare gli "elementi" per la tua sanità mentale e per la sanità mentale di altri che leggono il tuo codice.

Tuple<ApiResource, JSendResponseStatus> result = await SendApiRequest();
ApiResource apiResource = result.Item1;
JSendResponseStatus jSendStatus = result.Item2;

0

Penso che vorrei creare una classe ma un'altra alternativa sono i parametri di output.

public void GetOrderRelatedIds(out int OrderGroupId, out int OrderTypeId, out int OrderSubTypeId, out int OrderRequirementId)

Poiché la Tupla contiene solo numeri interi, è possibile rappresentarla con a Dictionary<string,int>

var orderIds = new Dictionary<string, int> {
    {"OrderGroupId", 1},
    {"OrderTypeId", 2},
    {"OrderSubTypeId", 3},
    {"OrderRequirementId", 4}.
};

ma non lo consiglio neanche a me.


0

Perché tutti rendono la vita così difficile. Le tuple servono per l' elaborazione dei dati piuttosto temporanea . Lavorare con Tuples in ogni momento renderà il codice molto difficile da capire ad un certo punto. Creare lezioni per tutto potrebbe eventualmente gonfiare il tuo progetto.

Si tratta di equilibrio, tuttavia ...

Il tuo problema sembra essere qualcosa per cui vorresti una lezione. E solo per completezza, questa classe di seguito contiene anche costruttori.


Questo è lo schema giusto per

  • Un tipo di dati personalizzato
    • senza ulteriori funzionalità. Getter e setter possono anche essere espansi con il codice, ottenendo / impostando membri privati ​​con il modello di nome "_orderGroupId", eseguendo anche il codice funzionale.
  • Compresi costruttori. Puoi anche scegliere di includere solo un costruttore, se tutto proprietà sono obbligatorie.
  • Se si desidera utilizzare tutti i costruttori, il gorgogliamento in questo modo è il modello corretto per evitare il codice duplicato.

public class OrderRelatedIds
{
    public int OrderGroupId { get; set; }
    public int OrderTypeId { get; set; }
    public int OrderSubTypeId { get; set; }
    public int OrderRequirementId { get; set; }

    public OrderRelatedIds()
    {
    }
    public OrderRelatedIds(int orderGroupId)
        : this()
    {
        OrderGroupId = orderGroupId;
    }
    public OrderRelatedIds(int orderGroupId, int orderTypeId)
        : this(orderGroupId)
    {
        OrderTypeId = orderTypeId;
    }
    public OrderRelatedIds(int orderGroupId, int orderTypeId, int orderSubTypeId)
        : this(orderGroupId, orderTypeId)
    {
        OrderSubTypeId = orderSubTypeId;
    }
    public OrderRelatedIds(int orderGroupId, int orderTypeId, int orderSubTypeId, int orderRequirementId)
        : this(orderGroupId, orderTypeId, orderSubTypeId)
    {
        OrderRequirementId = orderRequirementId;
    }
}

Oppure, se lo vuoi davvero semplice: puoi anche usare gli inizializzatori di tipo:

OrderRelatedIds orders = new OrderRelatedIds
{
    OrderGroupId = 1,
    OrderTypeId = 2,
    OrderSubTypeId = 3,
    OrderRequirementId = 4
};

public class OrderRelatedIds
{
    public int OrderGroupId;
    public int OrderTypeId;
    public int OrderSubTypeId;
    public int OrderRequirementId;
}

0

Scriverei i nomi degli oggetti in estate ... quindi passando con il mouse sopra la funzione helloworld () il testo dirà ciao = Item1 e world = Item2

 helloworld("Hi1,Hi2");

/// <summary>
/// Return hello = Item1 and world Item2
/// </summary>
/// <param name="input">string to split</param>
/// <returns></returns>
private static Tuple<bool, bool> helloworld(string input)
{
    bool hello = false;
    bool world = false;
    foreach (var hw in input.Split(','))
    {
        switch (hw)
        {
            case "Hi1":
                hello= true;
                break;
            case "Hi2":
                world= true;
                break;
        }

    }
    return new Tuple<bool, bool>(hello, world);
}

0

Solo per aggiungere alla risposta @MichaelMocko. Le tuple hanno un paio di gotcha al momento:

Non puoi usarli negli alberi delle espressioni EF

Esempio:

public static (string name, string surname) GetPersonName(this PersonContext ctx, int id)
{
    return ctx.Persons
        .Where(person => person.Id == id)
        // Selecting as Tuple
        .Select(person => (person.Name, person.Surname))
        .First();
}

Questo non verrà compilato con l'errore "Un albero delle espressioni potrebbe non contenere una tupla letterale" errore. Sfortunatamente, l'API degli alberi delle espressioni non è stata espansa con il supporto per le tuple quando queste sono state aggiunte alla lingua.

Traccia (e vota) questo problema per gli aggiornamenti: https://github.com/dotnet/roslyn/issues/12897

Per aggirare il problema, è possibile prima lanciarlo in tipo anonimo e poi convertire il valore in tupla:

// Will work
public static (string name, string surname) GetPersonName(this PersonContext ctx, int id)
{
    return ctx.Persons
        .Where(person => person.Id == id)
        .Select(person => new { person.Name, person.Surname })
        .ToList()
        .Select(person => (person.Name, person.Surname))
        .First();
}

Un'altra opzione è utilizzare ValueTuple.Create:

// Will work
public static (string name, string surname) GetPersonName(this PersonContext ctx, int id)
{
    return ctx.Persons
        .Where(person => person.Id == id)
        .Select(person => ValueTuple.Create(person.Name, person.Surname))
        .First();
}

Riferimenti:

Non puoi decostruirli in lambda

C'è una proposta per aggiungere il supporto: https://github.com/dotnet/csharplang/issues/258

Esempio:

public static IQueryable<(string name, string surname)> GetPersonName(this PersonContext ctx, int id)
{
    return ctx.Persons
        .Where(person => person.Id == id)
        .Select(person => ValueTuple.Create(person.Name, person.Surname));
}

// This won't work
ctx.GetPersonName(id).Select((name, surname) => { return name + surname; })

// But this will
ctx.GetPersonName(id).Select(t => { return t.name + t.surname; })

Riferimenti:

Non serializzeranno bene

using System;
using Newtonsoft.Json;

public class Program
{
    public static void Main() {
        var me = (age: 21, favoriteFood: "Custard");
        string json = JsonConvert.SerializeObject(me);

        // Will output {"Item1":21,"Item2":"Custard"}
        Console.WriteLine(json); 
    }
}

I nomi dei campi Tuple sono disponibili solo al momento della compilazione e vengono completamente cancellati in fase di esecuzione.

Riferimenti:


-1

Puoi scrivere una classe che contiene la Tupla.

È necessario sovrascrivere le funzioni Equals e GetHashCode

e gli operatori == e! =.

class Program
{
    public class MyTuple
    {
        private Tuple<int, int> t;

        public MyTuple(int a, int b)
        {
            t = new Tuple<int, int>(a, b);
        }

        public int A
        {
            get
            {
                return t.Item1;
            }
        }

        public int B
        {
            get
            {
                return t.Item2;
            }
        }

        public override bool Equals(object obj)
        {
            return t.Equals(((MyTuple)obj).t);
        }

        public override int GetHashCode()
        {
            return t.GetHashCode();
        }

        public static bool operator ==(MyTuple m1, MyTuple m2)
        {
            return m1.Equals(m2);
        }

        public static bool operator !=(MyTuple m1, MyTuple m2)
        {
            return !m1.Equals(m2);
        }
    }

    static void Main(string[] args)
    {
        var v1 = new MyTuple(1, 2);
        var v2 = new MyTuple(1, 2);

        Console.WriteLine(v1 == v2);

        Dictionary<MyTuple, int> d = new Dictionary<MyTuple, int>();
        d.Add(v1, 1);

        Console.WriteLine(d.ContainsKey(v2));
    }
}

sarà di ritorno:

Vero

Vero


2
Se hai già implementato una classe per questo tipo di dati, perché dichiari una Tupla per i dati sottostanti anziché solo le proprietà?
bytecode77,

Voglio usare l'attributo tupla che compara in base al valore nella funzione Equals
ss

Potrebbe essere un bonus. Ma d'altra parte hai praticamente creato una classe con proprietà che vanno da Item1 a ItemX. Sceglierei la denominazione corretta e più codice in Equals () piuttosto che usare una tupla.
bytecode77,

-1

Esempio di tupla C # 7

var tuple = TupleExample(key, value);

     private (string key1, long value1) ValidateAPIKeyOwnerId(string key, string value)
            {
                return (key, value);
            }
      if (!string.IsNullOrEmpty(tuple.key1) && tuple.value1 > 0)
          {
                    //your code

                }     
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.