Saya mendekompilasi beberapa perpustakaan C # 7 dan melihat ValueTuple
obat generik digunakan. Apa ValueTuples
dan mengapa tidak Tuple
?
Saya mendekompilasi beberapa perpustakaan C # 7 dan melihat ValueTuple
obat generik digunakan. Apa ValueTuples
dan mengapa tidak Tuple
?
Jawaban:
Apa
ValueTuples
dan mengapa tidakTuple
?
A ValueTuple
adalah struct yang mencerminkan tupel, sama dengan System.Tuple
kelas aslinya .
Perbedaan utama antara Tuple
dan ValueTuple
adalah:
System.ValueTuple
adalah tipe nilai (struct), sedangkan System.Tuple
adalah tipe referensi ( class
). Ini berarti ketika berbicara tentang alokasi dan tekanan GC.System.ValueTuple
bukan hanya a struct
, ini juga bisa berubah , dan seseorang harus berhati-hati saat menggunakannya. Pikirkan apa yang terjadi ketika kelas memegang System.ValueTuple
sebagai bidang.System.ValueTuple
mengekspos itemnya melalui bidang, bukan properti.Sampai C # 7, menggunakan tupel sangat tidak nyaman. Nama bidang mereka adalah Item1
,, Item2
dll, 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 ValueTuple
menjadi tipe nilai, Anda dapat menghindari tekanan GC saat menggunakannya karena (sebagai detail implementasi) mereka akan dialokasikan di stack.
Selain itu, struct
semantik persamaan otomatis mendapatkan (dangkal) oleh runtime, di mana a class
tidak. 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
structs
daripadaclasses
, sehingga tidak ada penalti alokasi yang terkait dengannya. Mereka harus seringan mungkin.Bisa dibilang,
structs
bisa jadi lebih mahal, karena nilai salinan tugas lebih besar. Jadi jika mereka ditugaskan lebih banyak daripada yang mereka buat, makastructs
akan 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.Tuple
menjadi 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 ValueTuple
ketika 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 Item1
dan 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 Tuple
and ValueTuple
is itu Tuple
adalah jenis referensi dan ValueTuple
merupakan 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 Tuple
dan ValueTuple
. Perbedaannya adalah bahwa itu Tuple
adalah class
dan ValueTuple
merupakan struct
yang mengimplementasikan IEquatable
.
Itu berarti itu Tuple == Tuple
akan kembali false
jika mereka bukan contoh yang sama, tetapi ValueTuple == ValueTuple
akan kembali true
jika mereka memiliki tipe yang sama dan Equals
mengembalikan true
untuk 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.Create
metode pabrik. The System.ValueTuple
jenis berbeda dari System.Tuple
jenis 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.Tuple
Anda 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.