Saya mendekompilasi beberapa perpustakaan C # 7 dan melihat ValueTuple
obat generik digunakan. Apa ValueTuples
dan mengapa tidak Tuple
?
144
Saya mendekompilasi beberapa perpustakaan C # 7 dan melihat ValueTuple
obat generik digunakan. Apa ValueTuples
dan mengapa tidak Tuple
?
Jawaban:
A
ValueTuple
adalah struct yang mencerminkan tupel, sama denganSystem.Tuple
kelas aslinya .Perbedaan utama antara
Tuple
danValueTuple
adalah:System.ValueTuple
adalah tipe nilai (struct), sedangkanSystem.Tuple
adalah tipe referensi (class
). Ini berarti ketika berbicara tentang alokasi dan tekanan GC.System.ValueTuple
bukan hanya astruct
, ini juga bisa berubah , dan seseorang harus berhati-hati saat menggunakannya. Pikirkan apa yang terjadi ketika kelas memegangSystem.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 aclass
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 desain
Tuples
:Contoh:
Anda dapat dengan mudah melihat bahwa bekerja dengan
System.Tuple
menjadi ambigu dengan sangat cepat. Misalnya, kita memiliki metode yang menghitung jumlah dan jumlah aList<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}");
Barang penyusun:
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
danItem2
, tetapi semua ini disarikan dari kita karena kita bekerja dengan tupel yang terdekomposisi. Sebuah tupel dengan argumen bernama mendapat anotasi denganTupleElementNamesAttribute
. 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
.sumber
var (sum, count) = DoStuff(Enumerable.Range(0, 10));
Perbedaan antara
Tuple
andValueTuple
is ituTuple
adalah jenis referensi danValueTuple
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.
sumber
Saya melihat sumber untuk
Tuple
danValueTuple
. Perbedaannya adalah bahwa ituTuple
adalahclass
danValueTuple
merupakanstruct
yang mengimplementasikanIEquatable
.Itu berarti itu
Tuple == Tuple
akan kembalifalse
jika mereka bukan contoh yang sama, tetapiValueTuple == ValueTuple
akan kembalitrue
jika mereka memiliki tipe yang sama danEquals
mengembalikantrue
untuk setiap nilai yang dikandungnya.sumber
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.
sumber
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. TheSystem.ValueTuple
jenis berbeda dariSystem.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";
sumber
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.
sumber