2 poin oleh GN⁺ 2024-10-20 | 1 komentar | Bagikan ke WhatsApp

-.NET 9.0 secara signifikan mengurangi waktu eksekusi dibanding .NET 8 dalam berbagai skenario LINQ umum, dan pada beberapa benchmark bahkan menghilangkan alokasi

  • Saat mengiterasi array atau List<T>, salah satu peningkatan utamanya adalah memperoleh ReadOnlySpan<T> melalui TryGetSpan() untuk menurunkan biaya iterasi
  • TryGetSpan() mengidentifikasi TSource[] dan List<TSource> lewat perbandingan tipe, tetapi cara mengambil span dari array internal List<T> adalah optimasi keluarga Unsafe yang bisa menjadi tidak valid saat kapasitas berubah -.LINQ di .NET 9 mengenali rantai pemanggilan umum untuk membuat iterator khusus, dan menerapkan optimasi tambahan pada metode terminal seperti Count(), First(), Last(), ElementAt(), Sum()
  • Hanya dengan migrasi sederhana dan kompilasi ulang, sebagian peningkatan performa LINQ bisa diperoleh; optimasi seperti pemanfaatan SIMD dan deteksi dini sekuens kosong juga disertakan

Alasan iterasi array dan list menjadi lebih cepat

  • Benchmark pertama menyimpan Enumerable.Range(1, 10_000).ToArray() sebagai IEnumerable<int>, lalu menjalankan Count, All, Any, First, Single, Last untuk membandingkan .NET 8 dan .NET 9
  • Benchmark ini menggunakan BenchmarkDotNet, dan proyek harus menargetkan net8.0;net9.0 serta dikompilasi dalam mode Release
  • Di .NET 9, waktu eksekusi banyak metode turun drastis dan alokasi juga hilang
    • LinqCount: dari 16,198.490 ns menjadi 3,043.563 ns, dari alokasi 32 B menjadi tanpa alokasi
    • LinqAny: dari 17,096.735 ns menjadi 2,483.927 ns, dari alokasi 32 B menjadi tanpa alokasi
    • LinqFirst: dari 15,289.747 ns menjadi 2,243.341 ns, dari alokasi 32 B menjadi tanpa alokasi
    • LinqSingle: dari 21,684.114 ns menjadi 4,884.329 ns, dari alokasi 32 B menjadi tanpa alokasi
    • LinqAll: dari 10.588 ns menjadi 2.562 ns, dari alokasi 32 B menjadi tanpa alokasi
    • LinqLast: dari 15.967 ns menjadi 6.918 ns

Perbedaan yang dibuat oleh TryGetSpan()

  • Penyebab utama peningkatan performa adalah penggunaan TryGetSpan()
  • Jika enumerable yang diiterasi adalah array atau list, TryGetSpan() mengembalikan ReadOnlySpan<T>, sehingga iterasi bisa lebih cepat
  • Kode percabangan inti memeriksa source.GetType() == typeof(TSource[]) atau source.GetType() == typeof(List<TSource>), lalu memperoleh span
    • Array ditangani dengan Unsafe.As<TSource[]>(source)
    • List memperoleh span dari array internal melalui CollectionsMarshal.AsSpan(Unsafe.As<List<TSource>>(source))
  • Dalam kode, source.GetType() dipanggil dua kali dan tidak ditangani dengan pemeriksaan null setelah casting, tetapi ini adalah pendekatan yang dipilih para pakar performa .NET dengan mempertimbangkan optimasi compiler C# dan JIT
  • Dalam stack .NET yang sangat dioptimalkan, optimasi mikro bisa menghasilkan efek yang berbeda dari tampilan sekilas

Batasan CollectionsMarshal.AsSpan()

  • List<TSource> secara internal merujuk ke sebuah array, dan saat kapasitas list harus bertambah atau berkurang, array baru dibuat lalu dirujuk
  • CollectionsMarshal.AsSpan(Unsafe.As<List<TSource>>(source)) memperoleh Span<TSource> dari array internal ini
  • Jika kapasitas list berubah dengan cara apa pun, array yang diperoleh lewat cara ini bisa menjadi tidak valid
  • Karena batasan ini, sebagian operasi Enumerable yang melibatkan iterasi tertunda seperti yield sulit bergantung pada optimasi ini
  • Nama System.Runtime.CompilerServices.Unsafe sendiri menunjukkan risiko semacam ini

Cakupan pemanggilan TryGetSpan()

  • Dengan memindai System.Linq.dll menggunakan NDepend, pemanggil langsung dan tidak langsung dari TryGetSpan() diperiksa
  • Path assembly yang dianalisis adalah C:\Program Files\dotnet\shared\Microsoft.NETCore.App\9.0.0-rc.1.24431.7\System.Linq.dll
  • Dari TryGetSpan(), code query dibuat untuk mengidentifikasi pemanggil, dan 56 metode yang cocok diekspor sebagai graf dependensi
  • Banyak metode standar Enumerable mencoba iterasi span saat koleksinya berupa array atau list
  • Namun karena cara menahan array internal list tidak aman, masih ada batasan untuk operasi yang memerlukan eksekusi tertunda

Optimasi berbasis iterator khusus

  • Benchmark kedua menggunakan contoh dari PR Consolidate LINQ’s internal IIListProvider/IPartition into base Iterator class
  • Target pengujian mencakup Distinct().First(), Append().Select().Last(), Reverse().Count(), DefaultIfEmpty().Select().ElementAt(), Skip().Take().ElementAt(), Union().First(), Select().Where().Select().Sum(), dan lainnya
  • Di .NET 9, sebagian rantai pemanggilan menjadi sangat jauh lebih cepat
    • DistinctFirst: dari 65.318 ns menjadi 11.192 ns, dari alokasi 328 B menjadi tanpa alokasi
    • AppendSelectLast: dari 4,122.007 ns menjadi 2.661 ns, dari alokasi 144 B menjadi tanpa alokasi
    • DefaultIfEmptySelectElementAt: dari 4,090.818 ns menjadi 5.724 ns, dari alokasi 144 B menjadi tanpa alokasi
    • RangeUnionFirst: dari 66.309 ns menjadi 6.193 ns, dari alokasi 344 B menjadi tanpa alokasi
    • ListSkipTakeElementAt: dari 6.268 ns menjadi 2.916 ns
    • RangeReverseCount: dari 11.024 ns menjadi 6.134 ns
  • Sebaliknya, SelectWhereSelectSum melambat dari 3,959.622 ns di .NET 8 menjadi 4,460.008 ns di .NET 9, dan alokasi 112 B juga tetap ada

Pengenalan rantai LINQ umum

  • Tim performa .NET merancang kode agar mengenali rantai pemanggilan LINQ yang umum
  • Saat rantai tertentu terdeteksi, iterator khusus yang menangani alur kerja dengan lebih efisien dibuat
  • Jika rantai berakhir dengan metode seperti Count(), First(), Last(), ElementAt(), Sum(), optimasi tambahan dapat dilakukan
  • Sebagai contoh, OrderBy(criteria).First() dapat dioptimalkan agar dieksekusi seperti Min(criteria)

Struktur Iterator<T> dan kelas turunannya

  • Di internal LINQ terdapat kelas dasar abstrak Iterator<T> dan 40 kelas turunan
  • Semua kelas ini bersarang di dalam kelas Enumerable
  • Iterator<T> adalah kelas abstrak, tetapi metodenya bersifat virtual, sehingga kelas turunan hanya meng-override metode yang diperlukan
  • Struktur ini menjadi dasar untuk menampung perilaku khusus per rantai pemanggilan

Contoh ListWhereSelectIterator<TSource, TResult>

  • ListWhereSelectIterator<TSource, TResult> menangani rantai Where(...).Select(...) pada list sebagai satu iterator
  • Iterator ini dibuat dari Select() override milik ListWhereIterator<TSource, TResult>
  • ListWhereIterator<TSource> dibuat saat Enumerable.Where() memeriksa apakah sumbernya adalah List<TSource>
  • ListWhereSelectIterator<TSource, TResult> tidak meng-override metode seperti TryGetFirst() atau TryGetLast()
  • Inti peningkatan performanya adalah menggabungkan rantai Where(...).Select(...) yang sangat umum pada list menjadi satu iterator, bukan dua iterator
    • Di dalam MoveNext(), dua delegate _predicate dan _selector dipanggil bersama

Contoh IListSkipTakeIterator<TSource>

  • IListSkipTakeIterator<TSource> adalah iterator khusus yang dibuat saat bisa diterapkan
  • MoveNext() menggunakan _state - 1 sebagai indeks berbasis 0 pada list
  • Memiliki field indeks terpisah akan lebih mudah dibaca, tetapi bias disimpan di _state untuk mengurangi ukuran field iterator
  • Optimasi iterator ini terletak pada tidak mengiterasi elemen yang tidak perlu di luar rentang _minIndexInclusive dan _maxIndexInclusive

Optimasi tambahan hanya dengan migrasi

  • Di .NET 9, berbagai skenario LINQ umum menjadi lebih cepat
  • Pekerjaan yang diperlukan untuk mendapatkan peningkatan dari versi .NET baru adalah migrasi dan kompilasi ulang
  • LINQ juga dioptimalkan dengan cara lain
    • Untuk kasus seperti penjumlahan sekuens integer, SIMD digunakan bila memungkinkan
    • Sekuens kosong dideteksi lebih awal sehingga biaya enumerasi turun
  • Video DeepDotnet dapat dilihat sebagai materi pembelajaran .NET yang menampilkan Scott Hanselman dan Stephen Toub

1 komentar

 
GN⁺ 2024-10-20
Opini Hacker News
  • Menurut saya bagian paling berguna dari LINQ bukanlah struktur ekstensi berbasis pohon sintaks pada IQueryable, juga bukan sintaks bawaan bahasa, melainkan metode ekstensi IEnumerable
    Dulu ini agak membingungkan disebut “LINQ to Objects”, dan memungkinkan C# ditulis secara ringkas dengan gaya fungsional
    Artikel aslinya terutama membahas optimisasi metode-metode ekstensi ini
    Baru setelah mempelajari Haskell, pendekatan ini terasa masuk akal bagi saya, dan pendekatan ini juga berbagi sebagian kelebihan serta jebakan Haskell seperti evaluasi malas
    Jika dipakai sembarangan, kodenya bisa menjadi sulit dipahami dan lambat, jadi saya tidak akan merekomendasikannya jika di tim tidak ada orang yang memahami idiom fungsional dasar dan evaluasi malas

    • Saya juga lebih menyukai sisi fungsional dari ekstensi LINQ untuk IEnumerable dan IQueryable
      Lebih mudah ditalar, dan di tempat seperti Entity Framework memang tidak selalu menjadi pilihan tercepat, tetapi umumnya merupakan opsi yang cukup bagus
      Saya juga lebih suka memakai Dapper daripada EF
      Namun proyek C# cenderung memiliki lapisan abstraksi yang kelewat banyak, dan pengembangan “enterprise” umumnya menyakitkan untuk dilihat
    • Saya juga memakai LINQ seperti ini
      Ada beberapa bagian penamaannya yang agak tidak standar, tetapi semua yang dibutuhkan sudah ada
      Eric Lippert menulis seri artikel yang sangat bagus tentang monad dalam kaitannya dengan LINQ: https://ericlippert.com/2013/04/02/monads-part-twelve/
    • Saya selalu hanya memakai sintaks metode untuk LINQ
      Saya tidak suka ada bahasa “bawaan” lain di dalam bahasa host, dan hasil akhirnya pada akhirnya juga harus kembali ke C#
      Bahkan ketika tidak memakai ORM seperti Entity Framework atau Dapper, logika akses data termasuk SQL sebaiknya ditempatkan di proyek terabstraksi yang terpisah
      Dengan begitu ia tidak menyebar ke seluruh aplikasi, dan bisa diganti ketika membutuhkan RDBMS lain
      Dalam 20 tahun, hal itu memang hanya terjadi sekali
      Saat developer junior memakai LINQ, memasangkan profiler dan debugger membantu mereka memahami apa yang terjadi di dalamnya
      Kadang juga berguna meminta mereka menulisnya terlebih dahulu dengan loop for dan logika C# biasa, lalu membandingkannya dengan implementasi LINQ, agar melihat kelebihan dan kekurangan kedua pendekatan
    • Jika Anda menyukai Haskell, Anda mungkin juga akan menyukai pemanfaatan lain seperti penyusunan parser kombinator menggunakan sintaks kueri LINQ
      Sintaks kueri tidak di-hardcode khusus untuk IEnumerable; hanya saja perilaku default-nya demikian, dan bisa dipakai hampir di mana saja
      Cara kerjanya agak mirip operator overloading
      [1]: https://github.com/acple/ParsecSharp/blob/da8d0cb9ec39e28dd9...
    • Kalau sintaks LINQ hilang besok, saya tidak akan terlalu merindukannya, tetapi komposisi fungsional benar-benar kuat dan juga lebih mudah dipelihara
  • Saya tidak mengerti mengapa tim dotnet tidak menginvestasikan lebih banyak sumber daya dan waktu pada tooling
    Kita membutuhkan doctest dan pembuatan dokumentasi, kemampuan menulis unit test yang lebih baik dan cepat di samping kode sebenarnya, aksesibilitas source code, lingkungan tempat menekan F12 tidak perlu mendekompilasi DLL, serta hub terpusat untuk paket dan dokumentasi seperti pkg.go.dev atau docs.rs
    Sebagian besar paket NuGet sama sekali tidak punya dokumentasi, atau hanya README GitHub, atau wiki singkat
    Ekosistem lain seperti Rust, Go, Java, dan Python berjarak beberapa tahun cahaya di depan dalam hal ini

    • Saya jadi bercanda bahwa Microsoft berinvestasi di OpenAI karena itu satu-satunya cara yang masuk akal untuk menjelajahi dokumentasi paket .NET/NuGet
      Namun yang mengerikan, rasanya itu mendekati benar
    • Sekarang dokumentasi Microsoft menyertakan tautan langsung ke source dari metode yang sedang dilihat
      Contoh: https://learn.microsoft.com/en-us/dotnet/api/system.string.s...
      Source paket NuGet juga bisa dibuat mudah dengan mengaktifkan Source Link, tetapi ini masih fitur yang relatif baru sehingga belum semua paket menerapkannya
    • Setuju, tetapi fakta bahwa C# open source masih relatif baru kemungkinan juga menjadi penyebabnya
      Saya rasa sebagian besar kode C# masih ditulis sebagai closed source di perusahaan
      Jika Microsoft terus bergerak ke arah yang lebih terbuka seperti beberapa tahun terakhir, saya pikir ini akan membaik seiring waktu
      Beberapa fitur seperti ini disediakan oleh tool seperti Resharper, dan saya penasaran apakah ada kesepakatan eksplisit atau implisit untuk tidak saling memasuki wilayah masing-masing
      Sejujurnya, sebagian besar dokumentasi yang saya lihat di proyek C# berkualitas rendah, sehingga akhirnya saya membaca source code
      Pengalaman saya, meskipun ada banyak tool autocomplete, itu tidak banyak membantu untuk membaca, hanya membantu untuk menulis
    • Sandcastle Help File Builder sudah ada sejak lama sekali, dan seingat saya dimulai sebagai proyek internal Microsoft, tetapi anehnya hanya sedikit library yang memakainya
      https://github.com/EWSoftware/SHFB
    • Ada juga cara menulis test di samping kode: https://clipperhouse.com/go-test-csharp/
      Saya tidak yakin akan merekomendasikannya
      Saya pernah mencobanya sendiri lalu membatalkannya, karena test tampaknya berjalan lebih lama, mungkin karena caching artefak build menjadi lebih buruk
  • Daripada “peningkatan performa LINQ”, lebih tepat disebut “peningkatan performa implementasi List mereka sendiri”
    Microsoft tampaknya lebih banyak menghabiskan waktu untuk memperbaiki bagian yang mereka butuhkan sendiri ketimbang peningkatan yang umum
    LINQ, khususnya sintaks kueri dan bukan sekadar ekstensi metode, membutuhkan investasi
    Terutama diperlukan pengurangan alokasi lambda, dan jika memungkinkan penyusutan lambda pada waktu kompilasi
    Diperlukan strategi agar lambda lokal bertipe nilai, atau alokasi lambda, tidak menjadi overhead seperti sekarang
    Variabel LINQ juga seharusnya sudah mendukung wildcard (_) sekarang, tetapi hal itu sepenuhnya diabaikan saat diperkenalkan ke lambda
    Selain itu, sebagai item terakhir dalam ekspresi LINQ, seharusnya bisa memakai tipe yang di-lift seperti IEnumerable atau Option, alih-alih select ...
    Pada kasus penggunaan tertentu, select menciptakan overhead yang tidak perlu, dan juga membatasi hal-hal seperti ekspresi LINQ rekursif ekor
    Library yang habis-habisan memakai LINQ seperti library saya, tetapi tidak memakai IEnumerable, IQueryable, atau ekstensi LINQ, terus diabaikan
    Itu karena Microsoft hanya berfokus pada peningkatan performa proyek mereka sendiri
    Contoh yang bagus adalah inferensi lambda yang ditingkatkan
    Itu dipercepat karena dibutuhkan oleh Minimal API di ASP.NET Core
    Tampaknya cukup banyak fitur bahasa dan framework digerakkan oleh kebutuhan internal Microsoft, bukan kebutuhan komunitas
    Yang terburuk adalah kumpulan metode ajaib seperti ekstensi LINQ Select, SelectMany, Where, dan juga GetAwaiter, terus bertambah
    Alih-alih memasukkan fitur higher-kinded typeclass yang benar-benar dibutuhkan untuk menghapus keajaiban ini, Microsoft menambahkan fitur untuk kepentingan mereka sendiri, terutama compiler
    Akibatnya semuanya tetap bertipe lemah, dan compiler hanya bisa menemukannya secara kira-kira
    LINQ adalah salah satu pembeda inti antarbahasa, tetapi hampir dibiarkan begitu saja sejak C# 3
    Sangat disayangkan bahwa LINQ masih dilihat hanya berguna untuk iterasi list, khususnya iterasi implementasi list milik mereka sendiri
    Peningkatan performanya sendiri patut diapresiasi dan akan membantu banyak pengguna, tetapi fokusnya selalu terlalu sempit sehingga membatasi potensinya
    [1] https://github.com/louthy/language-ext/

    • Jika ada masukan yang berguna, sebaiknya buat issue atau kirim PR ke dotnet/runtime
      Banyak peningkatan performa LINQ yang dibahas di artikel masuk dengan cara seperti itu
    • Library-nya terlihat sangat menarik, tetapi juga tampaknya sejak awal disusun dalam bentuk yang mudah diabaikan
      Ada banyak pernyataan using, dan itu bukan masalah besar bagi orang yang memahami cara membagi proyek menjadi unit-unit yang diperlukan dan memisahkan concern
      Namun sebagian besar developer tidak menstrukturkan proyek seperti itu, dan detail kecil semacam ini bisa menjadi penghalang bagi developer rata-rata
      Developer junior sering kali sudah kesulitan dengan sintaks dan metode LINQ standar, terutama juga soal performa
      Bagus bahwa README menyebutkan hal seperti ini
      Biasanya orang sibuk “menjual” library, jadi saya suka bahwa di sana benar-benar dituliskan di mana kekuatannya dan apa tujuannya
      Penyebutan bahwa ini tidak idiomatis juga bisa menjadi masalah bagi orang yang belajar C#/.NET
      Microsoft tampaknya ingin tooling dan bahasanya mengikuti praktik tertentu, dan penamaan yang secara alami disesuaikan dengan pemrograman fungsional bisa dianggap sebagai hambatan cukup besar saat Microsoft mempertimbangkan peningkatan
      Saya sudah memberi bintang pada repositorinya, dan sangat tertarik dengan apa yang dibuat
      Di beberapa aplikasi besar yang baru-baru ini saya buat, saya memakai tipe Result yang tampaknya kurang lebih mirip dengan Option
      Namun setelah melihat lagi library ini, saya merasa meskipun saya mengira cukup paham pemrograman fungsional, ternyata sebenarnya tidak
      Saya cukup mahir C# dan pernah mengerjakan hal-hal kompleks, tetapi pemrograman fungsional tetap sulit dikuasai meski sudah banyak membaca, dan F# For Fun And Profit sejauh ini paling mudah saya pahami
      Pada akhirnya, itu bukan berarti Anda melakukan sesuatu yang salah
      Microsoft akan menargetkan developer rata-rata atau pemula yang merupakan mayoritas ekosistemnya
      Semoga library ini bisa mendapatkan peningkatan internal
      Terlihat jelas ada waktu yang sangat besar dicurahkan, dan dari jumlah bintang GitHub saja sudah cukup menjadi sinyal bahwa ada orang yang benar-benar memakai dan terbantu olehnya
      Maaf jika terdengar seperti meremehkan, tetapi karya yang dibuat ini menarik dan dokumentasinya tampak cukup lengkap untuk mempelajari konsep-konsep yang belum saya ketahui secara perlahan
  • Semakin banyak C# meminjam dari F#, semakin baik
    Saya menunggu discriminated union akhirnya masuk ke C# agar domain modeling bisa dilakukan dengan benar

    • Menarik bahwa pembicaraan seperti ini sering muncul di komunitas .NET
      Setiap kali begitu, muncul pertanyaan “kenapa tidak pakai F# saja?”
      C# selama bertahun-tahun terus mengejar ketertinggalan
      Jika F# mendorong banyak inovasi di ekosistem .NET dan sudah beberapa tahun lebih maju dari sisi fitur, saya penasaran kenapa upaya itu tidak dihargai lewat penggunaan
      Jika ada arah pengembangan bahasa yang diinginkan, seharusnya itu didorong lewat pilihan nyata
      Di ekosistem Java, cara seperti itu berhasil, dan sekarang Java juga membaik
      Jika pasarnya membesar, bisa muncul loop umpan balik positif yang meningkatkan upaya engineering juga
      Setelah membaca forum selama bertahun-tahun, kesannya kubu C# sangat ingin “tetap saja” berada di kubunya sendiri dan menunggu
      Terlihat agak seperti tribalisme, seolah timnya adalah “C#”
      Saya jarang melihat budaya seperti ini di ekosistem bahasa lain, dan saya mendapat kesan bahwa jika F# berada di ekosistem selain .NET, mungkin sudah berkembang pesat sejak lama
    • Saya juga sangat menginginkan tipe satuan ukur
      Pemeliharaan kode rekayasa atau sains akan jauh lebih mudah
    • Dengan OneOf[0] dan Dunet[1], discriminated union sudah bisa diperkenalkan dengan cukup mudah
      Contoh penggunaan nyata: https://chrlschn.dev/blog/2024/07/csharp-discriminated-union...
      [0] https://github.com/mcintyre321/OneOf
      [1] https://github.com/domn1995/dunet
    • Sudah menjadi rahasia umum bahwa F# adalah tempat eksperimen untuk fitur C# dan VB.NET, dan figur resmi seperti Hanselman juga telah mengutipnya beberapa kali
  • Hal yang paling saya rindukan saat bekerja di bahasa atau ekosistem lain adalah LINQ
    Sangat menyenangkan ada fitur seperti ini di standard library, dan ia dirancang dengan indah dalam batasan yang ada

  • Ada bagian terkait dalam tulisan tahunan setebal satu buku yang membahas semua peningkatan performa di .NET 9
    https://devblogs.microsoft.com/dotnet/performance-improvemen...
    Anehnya HN tidak mengizinkan pengajuan ulang, jadi tulisannya tidak sempat naik ke front page dan terkubur

  • Setelah terbiasa dengan LINQ, dan biasanya bekerja di domain tempat LINQ benar-benar bersinar, rasanya tidak ingin kembali ke cara lain

    • Teman-teman, jangan sampai kecanduan LINQ
      LINQ akan mencengkeram kalian, dan kalian akan membenci lingkungan yang tidak punya LINQ
    • Meski begitu, ia masih kurang kuat dibanding sesuatu seperti polars
  • Saya penasaran apakah ada buku atau tutorial yang komprehensif untuk belajar pengembangan web end-to-end dengan dotnet
    Yang saya temukan kebanyakan terlalu dasar, sudah usang, atau kualitasnya rendah

    • Hal baru dan sedang hangat dalam pengembangan web .NET saat ini adalah Blazor, tetapi di luar lingkup blog Microsoft, ia tidak terlalu populer dan sepertinya tidak akan menjadi populer
      Secara pribadi, saya melihatnya akan menempuh jalan yang sama seperti Silverlight
      Teknologi lama juga masih ada di .NET 9, berfungsi, dan dipelihara
      Pengembangan web dengan .NET saat ini umumnya berarti membuat HTTP/JSON/REST API lalu menghubungkannya dengan framework frontend pilihan
      Dalam kasus saya, saya memakai React atau NextJS
      Kata kunci pencarian yang bagus adalah ASP.NET WebApi, atau yang lebih modern ASP.NET Minimal API
      Server-side rendering .NET MVC dengan Razor juga masih bisa dilakukan
      Karena itu bahasa markup untuk ASP.NET MVC, carilah dengan “ASP.NET MVC Razor”
    • Belakangan saya mulai tertarik pada pengembangan web dengan C#
      Baik atau buruk, cara membuat aplikasi web di .NET tampaknya pada dasarnya adalah ASP.NET
      Kurangnya alternatif memang sedikit mencurigakan
      Saya mendengarkan podcast yang menghadirkan Andrew Lock, penulis “ASP.NET Core in Action”, dan ia terdengar seperti orang yang benar-benar memahami materinya
      Saya belum membaca bukunya, tetapi mungkin itulah buku yang dicari
      1: https://dotnetcore.show/season-6/navigating-the-aspnet-core-...
      2: https://www.manning.com/books/asp-net-core-in-action-third-e...
    • Sedikit di luar arus utama, tetapi kombinasi F# dan Fable sangat kuat
      Di server, Anda bisa menjalankan Giraffe di atas ASP.NET, sebuah lapisan pemrograman fungsional dengan performa yang mirip C#
      Di frontend, Anda bisa menulis React dengan bahasa pemrograman fungsional sungguhan
      Tentu saja Anda juga bisa berbagi kode F# antara frontend dan backend
    • Saya belajar dengan langsung mencoba, tetapi berikut beberapa referensi yang layak dilihat
      Untuk buku, ada “C# 12 and .NET 8 - Modern Cross-Platform Development Fundamentals - Eighth Edition: Start building websites and services with ASP.NET Core 8, Blazor, and EF Core 8” karya Mark J Price, dan “Web API Development with ASP.NET Core 8: Learn techniques, patterns, and tools for building high-performance, robust, and scalable web APIs” karya Xiaodi Yan
      Untuk tutorial, ada seri IAmTimCorey dan Shawn Wildermuth di YouTube
    • Untuk UI yang dirender di server, carilah materi yang memakai Razor, dan sebaiknya hindari materi Blazor pada awalnya
      Jika kombinasinya backend .NET dan frontend JS, carilah materi yang memakai Minimal API
      MVC juga bagus, tetapi beban kompatibilitas mundurnya besar, sehingga Minimal API muncul
  • Pasti ada cara yang lebih baik daripada tumpukan mi komentar ini
    Setiap kali melihat kode .NET modern, mata saya sakit

    • Atribut-atribut itu sesuai dengan library benchmarking yang digunakan dalam tulisan
      Kode unit test dan benchmarking biasanya memang cenderung terlihat agak seperti spaghetti
      Meski begitu, saya tidak akan meloloskan PR yang seperti itu di business logic sungguhan
      Kalau benar-benar tidak suka, bahkan sesuatu seperti AspNetCore bisa dipakai tanpa menyentuh atribut sama sekali
    • Entah kode .NET seperti apa yang sedang Anda lihat
      Saya hampir tidak pernah memakai atribut
  • Bagian bahwa lebih banyak optimisasi bisa dilakukan ketika chain berakhir dengan metode seperti Count(), First(), Last(), ElementAt(), Sum(), dan misalnya OrderBy(criteria).First() bisa dioptimalkan agar berjalan seperti Min(criteria), mungkin berguna
    Tetapi sejak awal, seharusnya tulislah kode yang lebih baik
    Ini menarik untuk chain yang dibuat secara dinamis, tetapi jika operasi seperti ini ada di kode yang ditulis langsung, rasanya seperti penguatan positif yang agak menyimpang
    Library seolah mengenali pola yang tidak efisien lalu memperbaikinya
    Setidaknya saya berharap ada umpan balik yang menyarankan perbaikan pada kode dasarnya