Saya mendekompilasi beberapa perpustakaan C # 7 dan melihat ValueTupleobat generik digunakan. Apa ValueTuplesdan mengapa tidak Tuple?
Saya mendekompilasi beberapa perpustakaan C # 7 dan melihat ValueTupleobat generik digunakan. Apa ValueTuplesdan mengapa tidak Tuple?
Jawaban:
Apa
ValueTuplesdan mengapa tidakTuple?
A ValueTupleadalah struct yang mencerminkan tupel, sama dengan System.Tuplekelas aslinya .
Perbedaan utama antara Tupledan ValueTupleadalah:
System.ValueTupleadalah tipe nilai (struct), sedangkan System.Tupleadalah tipe referensi ( class). Ini berarti ketika berbicara tentang alokasi dan tekanan GC.System.ValueTuplebukan hanya a struct, ini juga bisa berubah , dan seseorang harus berhati-hati saat menggunakannya. Pikirkan apa yang terjadi ketika kelas memegang System.ValueTuplesebagai bidang.System.ValueTuple mengekspos itemnya melalui bidang, bukan properti.Sampai C # 7, menggunakan tupel sangat tidak nyaman. Nama bidang mereka adalah Item1,, Item2dll, dan bahasa tersebut belum menyediakan gula sintaks untuk mereka seperti kebanyakan bahasa lain (Python, Scala).
Ketika tim desain bahasa .NET memutuskan untuk menggabungkan tupel dan menambahkan gula sintaks ke mereka di tingkat bahasa, faktor penting adalah kinerja. Dengan ValueTuplemenjadi tipe nilai, Anda dapat menghindari tekanan GC saat menggunakannya karena (sebagai detail implementasi) mereka akan dialokasikan di stack.
Selain itu, structsemantik persamaan otomatis mendapatkan (dangkal) oleh runtime, di mana a classtidak. Meskipun tim desain memastikan akan ada kesetaraan yang lebih optimal untuk tupel, oleh karena itu menerapkan persamaan khusus untuk itu.
Berikut adalah paragraf dari catatan desainTuples :
Struct atau Class:
Seperti yang disebutkan, saya mengusulkan untuk membuat jenis tupel
structsdaripadaclasses, sehingga tidak ada penalti alokasi yang terkait dengannya. Mereka harus seringan mungkin.Bisa dibilang,
structsbisa jadi lebih mahal, karena nilai salinan tugas lebih besar. Jadi jika mereka ditugaskan lebih banyak daripada yang mereka buat, makastructsakan menjadi pilihan yang buruk.Namun, dalam motivasi mereka, tupel bersifat sementara. Anda akan menggunakannya saat bagian-bagiannya lebih penting daripada keseluruhan. Jadi pola yang umum adalah membangun, mengembalikan dan segera mendekonstruksinya. Dalam situasi ini, struct jelas lebih disukai.
Struktur juga memiliki sejumlah manfaat lain, yang akan dijelaskan berikut ini.
Anda dapat dengan mudah melihat bahwa bekerja dengan System.Tuplemenjadi ambigu dengan sangat cepat. Misalnya, kita memiliki metode yang menghitung jumlah dan jumlah a List<Int>:
public Tuple<int, int> DoStuff(IEnumerable<int> values)
{
var sum = 0;
var count = 0;
foreach (var value in values) { sum += value; count++; }
return new Tuple(sum, count);
}
Di sisi penerima, kami berakhir dengan:
Tuple<int, int> result = DoStuff(Enumerable.Range(0, 10));
// What is Item1 and what is Item2?
// Which one is the sum and which is the count?
Console.WriteLine(result.Item1);
Console.WriteLine(result.Item2);
Cara Anda mendekonstruksi tupel nilai menjadi argumen bernama adalah kekuatan sebenarnya dari fitur tersebut:
public (int sum, int count) DoStuff(IEnumerable<int> values)
{
var res = (sum: 0, count: 0);
foreach (var value in values) { res.sum += value; res.count++; }
return res;
}
Dan di pihak penerima:
var result = DoStuff(Enumerable.Range(0, 10));
Console.WriteLine($"Sum: {result.Sum}, Count: {result.Count}");
Atau:
var (sum, count) = DoStuff(Enumerable.Range(0, 10));
Console.WriteLine($"Sum: {sum}, Count: {count}");
Jika kita melihat di bawah sampul contoh kita sebelumnya, kita dapat melihat dengan tepat bagaimana kompilator menafsirkan ValueTupleketika kita memintanya untuk mendekonstruksi:
[return: TupleElementNames(new string[] {
"sum",
"count"
})]
public ValueTuple<int, int> DoStuff(IEnumerable<int> values)
{
ValueTuple<int, int> result;
result..ctor(0, 0);
foreach (int current in values)
{
result.Item1 += current;
result.Item2++;
}
return result;
}
public void Foo()
{
ValueTuple<int, int> expr_0E = this.DoStuff(Enumerable.Range(0, 10));
int item = expr_0E.Item1;
int arg_1A_0 = expr_0E.Item2;
}
Secara internal, kode yang dikompilasi menggunakan Item1dan Item2, tetapi semua ini disarikan dari kita karena kita bekerja dengan tupel yang terdekomposisi. Sebuah tupel dengan argumen bernama mendapat anotasi dengan TupleElementNamesAttribute. Jika kita menggunakan satu variabel segar daripada mendekomposisi, kita mendapatkan:
public void Foo()
{
ValueTuple<int, int> valueTuple = this.DoStuff(Enumerable.Range(0, 10));
Console.WriteLine(string.Format("Sum: {0}, Count: {1})", valueTuple.Item1, valueTuple.Item2));
}
Perhatikan bahwa compiler masih harus membuat beberapa keajaiban terjadi (melalui atribut) ketika kita men-debug aplikasi kita, karena akan menjadi aneh melihat Item1, Item2.
var (sum, count) = DoStuff(Enumerable.Range(0, 10));
Perbedaan antara Tupleand ValueTupleis itu Tupleadalah jenis referensi dan ValueTuplemerupakan jenis nilai. Yang terakhir ini diinginkan karena perubahan pada bahasa di C # 7 membuat tupel lebih sering digunakan, tetapi mengalokasikan objek baru di heap untuk setiap tupel adalah masalah performa, terutama jika tidak diperlukan.
Namun, di C # 7, idenya adalah bahwa Anda tidak perlu secara eksplisit menggunakan kedua jenis karena gula sintaks ditambahkan untuk penggunaan tupel. Misalnya, di C # 6, jika Anda ingin menggunakan tupel untuk mengembalikan nilai, Anda harus melakukan hal berikut:
public Tuple<string, int> GetValues()
{
// ...
return new Tuple(stringVal, intVal);
}
var value = GetValues();
string s = value.Item1;
Namun, di C # 7, Anda dapat menggunakan ini:
public (string, int) GetValues()
{
// ...
return (stringVal, intVal);
}
var value = GetValues();
string s = value.Item1;
Anda bahkan dapat melangkah lebih jauh dan memberikan nama nilai:
public (string S, int I) GetValues()
{
// ...
return (stringVal, intVal);
}
var value = GetValues();
string s = value.S;
... Atau dekonstruksi tupel seluruhnya:
public (string S, int I) GetValues()
{
// ...
return (stringVal, intVal);
}
var (S, I) = GetValues();
string s = S;
Tuple tidak sering digunakan di C # pre-7 karena rumit dan bertele-tele, dan hanya benar-benar digunakan dalam kasus di mana membangun kelas / struct data hanya untuk satu contoh pekerjaan akan lebih merepotkan daripada nilainya. Tetapi di C # 7, tupel memiliki dukungan tingkat bahasa sekarang, jadi menggunakannya jauh lebih bersih dan lebih berguna.
Saya melihat sumber untuk Tupledan ValueTuple. Perbedaannya adalah bahwa itu Tupleadalah classdan ValueTuplemerupakan structyang mengimplementasikan IEquatable.
Itu berarti itu Tuple == Tupleakan kembali falsejika mereka bukan contoh yang sama, tetapi ValueTuple == ValueTupleakan kembali truejika mereka memiliki tipe yang sama dan Equalsmengembalikan trueuntuk setiap nilai yang dikandungnya.
Selain komentar di atas, satu hal yang tidak menguntungkan dari ValueTuple adalah, sebagai tipe nilai, argumen bernama dihapus saat dikompilasi ke IL, sehingga tidak tersedia untuk serialisasi pada waktu proses.
yaitu, argumen manis Anda akan tetap berakhir sebagai "Item1", "Item2", dll. ketika diserialkan melalui mis. Json.NET.
Jawaban lain lupa menyebutkan poin penting Alih-alih mengulanginya, saya akan mereferensikan dokumentasi XML dari kode sumber :
Jenis ValueTuple (dari arity 0 hingga 8) terdiri dari implementasi runtime yang mendasari tupel di C # dan struct tuple di F #.
Selain dibuat melalui sintaks bahasa , mereka paling mudah dibuat melalui
ValueTuple.Createmetode pabrik. The System.ValueTuplejenis berbeda dari System.Tuplejenis di bahwa:
Dengan pengenalan tipe ini dan kompiler C # 7.0, Anda dapat dengan mudah menulis
(int, string) idAndName = (1, "John");
Dan mengembalikan dua nilai dari sebuah metode:
private (int, string) GetIdAndName()
{
//.....
return (id, name);
}
Berlawanan dengan System.TupleAnda dapat memperbarui anggotanya (Dapat diubah) karena mereka adalah bidang baca-tulis publik yang dapat diberi nama yang bermakna:
(int id, string name) idAndName = (1, "John");
idAndName.name = "New Name";
class MyNonGenericType : MyGenericType<string, ValueTuple, int>dll.
Bergabung terlambat untuk menambahkan klarifikasi cepat pada dua factoids ini:
Orang akan berpikir bahwa mengubah nilai-tupel secara massal akan sangat mudah:
foreach (var x in listOfValueTuples) { x.Foo = 103; } // wont even compile because x is a value (struct) not a variable
var d = listOfValueTuples[0].Foo;
Seseorang mungkin mencoba untuk mengatasinya seperti ini:
// initially *.Foo = 10 for all items
listOfValueTuples.Select(x => x.Foo = 103);
var d = listOfValueTuples[0].Foo; // 'd' should be 103 right? wrong! it is '10'
Alasan untuk perilaku unik ini adalah karena nilai-tupel persis berbasis nilai (struct) dan dengan demikian panggilan .Select (...) berfungsi pada clone-struct bukan pada aslinya. Untuk mengatasi ini, kita harus menggunakan:
// initially *.Foo = 10 for all items
listOfValueTuples = listOfValueTuples
.Select(x => {
x.Foo = 103;
return x;
})
.ToList();
var d = listOfValueTuples[0].Foo; // 'd' is now 103 indeed
Atau, tentu saja, seseorang dapat mencoba pendekatan langsung:
for (var i = 0; i < listOfValueTuples.Length; i++) {
listOfValueTuples[i].Foo = 103; //this works just fine
// another alternative approach:
//
// var x = listOfValueTuples[i];
// x.Foo = 103;
// listOfValueTuples[i] = x; //<-- vital for this alternative approach to work if you omit this changes wont be saved to the original list
}
var d = listOfValueTuples[0].Foo; // 'd' is now 103 indeed
Semoga ini membantu seseorang yang berjuang untuk membuat head of tail out of value-tuple yang dihosting daftar.