Penamaan yang lebih baik di kelas Tuple daripada "Item1", "Item2"


204

Apakah ada cara untuk menggunakan kelas Tuple, tetapi berikan nama-nama item di dalamnya?

Sebagai contoh:

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

Itu mengembalikan id untuk OrderGroupId, OrderTypeId, OrderSubTypeId, dan OrderRequirementId.

Akan menyenangkan untuk memberi tahu pengguna metode saya mana yang mana. (Ketika Anda memanggil metode, hasilnya adalah result.Item1, result.Item2, result.Item3, result.Item4. Tidak jelas mana yang mana.)

(Saya tahu saya hanya bisa membuat kelas untuk menampung semua Id ini, tetapi Id ini sudah memiliki kelas mereka sendiri dan membuat kelas untuk nilai pengembalian metode yang satu ini tampak konyol.)


1
Anda harus memutar sendiri - Tuplesangat generik, jadi hanya itu yang Anda dapatkan
BrokenGlass

NO Anda tidak dapat melakukan seperti itu, lihat link ini untuk informasi lebih lanjut msdn.microsoft.com/en-us/vcsharp/ee957397
Enigma Negara

1
Saya berani mengatakan bahwa menggunakan Tuple sebagai tipe data publik untuk api Anda mungkin tidak disarankan. Saya biasanya menggunakan Tuple untuk hal-hal internal yang berumur pendek bukan sebagai nilai balik API.
Mike Burdick


4
Itu ada dalam daftar kerja C # 7 Lihat github.com/dotnet/roslyn/issues/347
Philip Ding

Jawaban:


277

Di C # 7.0 (Visual Studio 2017) ada konstruksi baru untuk melakukan itu:

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

68
Sintaksnya adalah List<(int first, int second)>. Saya harus mengunduh paket System.ValueTuple dari NuGet untuk membuatnya berfungsi di Visual Studio 2017.
Matt Davis

14
Untuk membuat nilaireturn (first: first, middle: middle, last: last);
fiat

4
atau hanya: return (first, middle, last);di .NET 4.7.1 (tidak yakin untuk 4.7.0)
watbywbarif

1
untuk menggunakannya, Anda perlu menambahkan paket nuget System.ValueTuple
Alex G

11
Perlu dicatat bahwa C # 7's ValueTuple, walaupun biasanya hebat, adalah tipe nilai yang dapat berubah (struct), sementara itu Tupleadalah tipe referensi yang tidak dapat diubah (kelas). Sejauh yang saya tahu, tidak ada cara untuk mendapatkan tipe referensi Tupledengan nama item yang ramah.
dx_over_dt

51

Hingga C # 7.0, tidak ada cara untuk melakukan ini dengan mendefinisikan tipe Anda sendiri.


13
Saya tidak percaya jawaban ini diterima dengan skor 40. Anda bisa setidaknya menunjukkan bagaimana kelas dengan konstruktor yang tepat dapat menggantikan ini.
bytecode77

1
@ bytecode77 Baiklah, segera jawaban ini akan langsung salah: github.com/dotnet/roslyn/issues/347
MarkPflug

Saya telah melihat proposal perayaan bahasa ini. Tetapi sampai sekarang, kelas adalah satu-satunya solusi yang tepat untuk tipe data yang lebih kompleks. Mengapa Anda ingin menggunakan Tuples secara paksa, apa pun yang terjadi (lihat jawaban lain)
bytecode77

3
Setelah C # 7 dirilis, dimungkinkan untuk melakukan ini: msdn.microsoft.com/en-us/magazine/mt595758.aspx
Burak Karakuş

11
Q 'memiliki c # 4 sebagai tag, jadi meskipun jawaban ini singkat namun tetap benar.
Steve Drake

33

Ini adalah versi yang terlalu rumit dari apa yang Anda tanyakan:

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

}

Mengapa tidak membuat kelas saja?


2
apakah struct akan lebih baik dalam kasus ini daripada Kelas?
deathrace

5
Kelebihan kecil yang saya lihat dari ini adalah bahwa ia secara otomatis mengimplementasikan operator yang sama, memeriksa bahwa 2 instance sama jika semua item sama.
JSoet

8
Kelemahan lain untuk pendekatan ini adalah bahwa Item1 dan Item2 masih properti publik pada MyTuple
RJFalconer

3
@deathrace Tuple sendiri adalah kelas, jadi jika Anda ingin langsung mewarisi dari Tuple<T, T2>Anda tidak bisa menjadi struct.
Chakrava

3
Saya mungkin salah tetapi saya kebanyakan menggunakan tuple di mana pun saya ingin mengembalikan objek tetapi tidak ingin mendefinisikan kelas tertentu ..
Jay

12

Dengan .net 4 Anda mungkin dapat melihat ExpandoObject, namun, jangan menggunakannya untuk kasus sederhana ini karena apa yang seharusnya menjadi kesalahan waktu kompilasi menjadi kesalahan run-time.

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.

Sesuatu yang layak disebutkan adalah tipe anonim untuk dalam suatu metode , tetapi Anda perlu membuat kelas jika Anda ingin mengembalikannya.

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

10

Mereproduksi jawaban saya dari pos ini karena lebih cocok di sini.

Mulai C # v7.0, sekarang dimungkinkan untuk memberi nama properti tuple yang sebelumnya digunakan sebagai nama standar seperti Item1, Item2dan seterusnya.

Memberi nama properti Tuple Literals :

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

Output pada konsol:

Nama - RBT_Yoga, Usia - 22, Gairah - Dosa

Mengembalikan Tuple (memiliki properti yang diberi nama) dari metode :

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);
}

Output pada konsol:

Rincian Karyawan: Rasik, Bihari, Rasik-PC, 1000

Membuat daftar Tuples yang memiliki properti bernama

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}");

Output pada konsol:

1 - sapi 5 - ayam 1 - pesawat terbang

Saya harap saya sudah membahas semuanya. Dalam hal, ada sesuatu yang saya lewatkan maka tolong beri saya umpan balik dalam komentar.

Catatan : Cuplikan kode saya menggunakan fitur interpolasi string C # v7 seperti yang dijelaskan di sini .


3

MichaelMocko Dijawab hebat,

tapi saya ingin menambahkan beberapa hal yang harus saya pahami

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

Baris di atas akan memberi Anda waktu kompilasi kesalahan jika Anda menggunakan .net framework <4.7

Jadi jika Anda memiliki proyek yang menggunakan .net framework <4.7 dan Anda masih ingin menggunakan ValueTuple daripada workAround akan menginstal paket nuget ini



2

Jika jenis barang Anda semuanya berbeda, ini adalah kelas yang saya buat untuk mendapatkannya secara lebih intuitif.

Penggunaan kelas ini:

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

Kode sumber:

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
Ini sepertinya banyak pekerjaan untuk sedikit atau tanpa hasil. Ini memiliki batasan tidak intuitif (tidak ada tipe duplikat), dan saya menemukan ide untuk mengambil nilai hanya dengan jenisnya saja sangat tidak intuitif dan tidak bisa memikirkan kasus penggunaan praktis untuk itu. Ini setara dengan membuat tabel data untuk karyawan, kemudian memutuskan untuk mengambil karyawan dengan nama depan mereka (bukan kunci unik) dan selanjutnya mengharuskan semua karyawan untuk memiliki nama depan yang berbeda. Ini bukan solusi untuk masalah, itu menggunakan solusi dengan biaya menciptakan masalah tambahan.
Flater

Dan semoga Tuhan mengampuni jiwa Anda.
Jamie M.

1

Ini sangat menjengkelkan dan saya berharap versi C # yang akan datang akan menjawab kebutuhan ini. Saya menemukan pekerjaan yang paling mudah adalah menggunakan tipe struktur data yang berbeda atau mengganti nama "item" untuk kewarasan Anda dan untuk kewarasan orang lain yang membaca kode Anda.

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

0

Saya pikir saya akan membuat kelas tetapi alternatif lain adalah parameter output.

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

Karena Tuple Anda hanya berisi bilangan bulat, Anda dapat mewakilinya dengan a Dictionary<string,int>

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

tapi saya juga tidak merekomendasikan itu.


0

Mengapa semua orang membuat hidup begitu sulit. Tuples untuk pemrosesan data yang agak sementara . Bekerja dengan Tuples setiap saat akan membuat kode ini sangat sulit untuk dipahami di beberapa titik. Membuat kelas untuk semuanya pada akhirnya bisa mengasapi proyek Anda.

Ini tentang keseimbangan, namun ...

Masalah Anda tampaknya adalah sesuatu yang Anda inginkan untuk kelas. Dan hanya demi kelengkapan, kelas di bawah ini juga berisi konstruktor.


Ini adalah pola yang tepat untuk

  • Tipe data khusus
    • tanpa fungsi lebih lanjut. Getters dan setter juga dapat diperluas dengan kode, mendapatkan / menetapkan anggota pribadi dengan pola nama "_orderGroupId", sementara juga mengeksekusi kode fungsional.
  • Termasuk konstruktor. Anda juga dapat memilih untuk memasukkan hanya satu konstruktor jika semua properti wajib.
  • Jika Anda ingin menggunakan semua konstruktor, menggelegak seperti ini adalah pola yang tepat untuk menghindari kode duplikat.

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

Atau, jika Anda menginginkannya sangat sederhana: Anda juga dapat menggunakan jenis inisialisasi:

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

Saya akan menulis nama-nama Item di summay .. jadi dengan melayang di atas fungsi helloworld () teks akan menyapa = Item1 dan 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

Hanya dengan menambahkan jawaban @MichaelMocko. Tuples punya beberapa gotcha saat ini:

Anda tidak dapat menggunakannya di pohon ekspresi EF

Contoh:

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();
}

Ini akan gagal dikompilasi dengan "Pohon ekspresi mungkin tidak mengandung kesalahan tuple literal". Sayangnya, pohon ekspresi API tidak diperluas dengan dukungan untuk tupel ketika ini ditambahkan ke bahasa.

Lacak (dan upvote) masalah ini untuk pembaruan: https://github.com/dotnet/roslyn/issues/12897

Untuk mengatasi masalah tersebut, Anda dapat melemparkannya ke jenis anonim terlebih dahulu dan kemudian mengonversi nilainya menjadi tuple:

// 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();
}

Opsi lain adalah menggunakan 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();
}

Referensi:

Anda tidak dapat mendekonstruksi mereka dalam lambda

Ada proposal untuk menambahkan dukungan: https://github.com/dotnet/csharplang/issues/258

Contoh:

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; })

Referensi:

Mereka tidak akan membuat serial dengan baik

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); 
    }
}

Nama bidang Tuple hanya tersedia pada waktu kompilasi dan sepenuhnya dihapus pada saat runtime.

Referensi:


-1

Anda dapat menulis kelas yang berisi Tuple.

Anda perlu mengganti fungsi Equals dan GetHashCode

dan operator == dan! =.

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));
    }
}

akan kembali:

Benar

Benar


2
Jika Anda sudah menerapkan kelas untuk tipe data ini, mengapa Anda mendeklarasikan Tuple untuk data yang mendasarinya, bukan hanya properti?
bytecode77

Saya ingin menggunakan atribut tuple yang ia comper dengan nilai dalam fungsi Equals
ss

Itu mungkin bonus. Tetapi di sisi lain pada dasarnya Anda membuat kelas dengan properti yang berkisar dari Item1 ke ItemX. Saya akan memilih penamaan yang tepat dan lebih banyak kode dalam Equals () daripada menggunakan tuple.
bytecode77

-1

Contoh tuple 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

                }     
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.