Preferensi Gaya LINQ [ditutup]

21

Saya datang untuk menggunakan LINQ dalam pemrograman saya setiap hari. Bahkan, saya jarang, jika pernah, menggunakan loop eksplisit. Saya telah, bagaimanapun, menemukan bahwa saya tidak menggunakan sintaks seperti SQL lagi. Saya hanya menggunakan fungsi ekstensi. Jadi daripada mengatakan:

from x in y select datatransform where filter 

Saya menggunakan:

x.Where(c => filter).Select(c => datatransform)

Gaya LINQ apa ​​yang Anda sukai dan apa yang membuat orang lain nyaman dengan tim Anda?

Erin
sumber
5
Mungkin perlu dicatat bahwa sikap MS resmi adalah bahwa sintaks kueri lebih disukai.
R0MANARMY
1
Pada akhirnya itu tidak masalah. Yang penting adalah bahwa kode itu dapat dimengerti. Satu bentuk mungkin lebih baik dalam satu kasus, yang lain dalam kasus yang berbeda. Jadi gunakan mana yang sesuai pada saat itu.
ChrisF
Saya percaya contoh kedua Anda disebut sintaks lambda, yang mana saya gunakan 95% dari waktu. 5% lainnya saya menggunakan sintaks kueri yang ketika saya melakukan bergabung, saya mencoba untuk transisi ke sintaks lambda bergabung tetapi seperti orang lain telah menunjukkan itu menjadi berantakan.
The Muffin Man

Jawaban:

26

Saya merasa tidak beruntung bahwa sikap Microsoft per dokumentasi MSDN adalah bahwa sintaks kueri lebih disukai, karena saya tidak pernah menggunakannya, tetapi saya menggunakan sintaksis metode LINQ sepanjang waktu. Saya senang bisa memunculkan pertanyaan satu-liner ke isi hati saya. Membandingkan:

var products = from p in Products
               where p.StockOnHand == 0
               select p;

Untuk:

var products = Products.Where(p => p.StockOnHand == 0);

Lebih cepat, lebih sedikit garis, dan mata saya terlihat lebih bersih. Sintaks kueri juga tidak mendukung semua operator LINQ standar. Contoh kueri yang baru-baru ini saya lakukan terlihat seperti ini:

var itemInfo = InventoryItems
    .Where(r => r.ItemInfo is GeneralMerchInfo)
    .Select(r => r.ItemInfo)
    .Cast<GeneralMerchInfo>()
    .FirstOrDefault(r => r.Xref == xref);

Sepengetahuan saya, untuk mereplikasi kueri ini menggunakan sintaks kueri (sejauh mungkin) akan terlihat seperti ini:

var itemInfo = (from r in InventoryItems
                where r.ItemInfo is GeneralMerchInfo
                select r.ItemInfo)
                .Cast<GeneralMerchInfo>()
                .FirstOrDefault(r => r.Xref == xref);

Tidak terlihat lebih mudah dibaca oleh saya, dan Anda harus tahu bagaimana cara menggunakan sintaks metode. Secara pribadi saya benar-benar terpikat pada gaya deklaratif yang dimungkinkan oleh LINQ dan menggunakannya dalam situasi apa pun yang memungkinkan - mungkin kadang-kadang merugikan saya. Contoh kasus, dengan sintaks metode saya dapat melakukan sesuatu seperti ini:

// projects an InventoryItem collection with total stock on hand for each GSItem
inventoryItems = repository.GSItems
    .Select(gsItem => new InventoryItem() {
        GSItem = gsItem,
        StockOnHand = repository.InventoryItems
            .Where(inventoryItem => inventoryItem.GSItem.GSNumber == gsItem.GSNumber)
            .Sum(r => r.StockOnHand)
     });

Saya membayangkan kode di atas akan sulit dipahami bagi seseorang yang datang ke proyek tanpa dokumentasi yang baik, dan jika mereka tidak memiliki latar belakang yang kuat di LINQ, mereka mungkin tidak memahaminya. Namun, sintaksis metode memperlihatkan beberapa kemampuan yang cukup kuat, untuk dengan cepat (dalam hal baris kode) memproyeksikan kueri untuk mendapatkan informasi agregat tentang beberapa koleksi yang jika tidak akan memerlukan banyak perulangan perulangan yang membosankan. Dalam kasus seperti ini, sintaksis metode sangat kompak untuk apa yang Anda dapatkan darinya. Mencoba melakukan ini dengan sintaks kueri mungkin menjadi agak sulit dengan cepat.

klir2m
sumber
Para pemain dapat Anda lakukan di dalam pilih tetapi sayangnya Anda tidak dapat menentukan untuk mengambil catatan X teratas tanpa menggunakan metode LINQ. Ini sangat menjengkelkan di tempat-tempat di mana Anda tahu Anda hanya perlu satu catatan dan harus meletakkan semua kueri dalam tanda kurung.
Ziv
2
Sebagai catatan, Anda dapat melakukan Select (x => x.ItemInfo) .OfType <GeneralMerchInfo> () alih-alih dari Where (). Select (). Cast <> (), yang saya percaya lebih cepat (big O dari 2n bukannya n * 2m saya pikir). Tapi Anda sepenuhnya benar, sintaks lambda jauh lebih baik dari sudut pandang keterbacaan.
Ed James
16

Saya menemukan sintaks fungsional lebih menyenangkan pada mata. Satu-satunya pengecualian adalah jika saya harus bergabung lebih dari dua set. Gabung () menjadi gila dengan sangat cepat.

John Kraft
sumber
Setuju ... Saya lebih suka tampilan dan keterbacaan dari metode ekstensi kecuali (seperti yang ditunjukkan) ketika bergabung. Vendor komponen (misalnya Telerik) banyak menggunakan metode ekstensi. Contoh yang saya pikirkan adalah Kontrol Rad mereka di ASP.NET MVC. Anda harus sangat mahir menggunakan metode ekstensi untuk menggunakan / membaca itu.
Catchops
Datang untuk mengatakan ini. Saya biasanya menggunakan lambdas kecuali jika ada yang terlibat. Setelah bergabung, sintaksis LINQ cenderung lebih mudah dibaca.
Sean
10

Apakah sudah terlambat untuk menambahkan jawaban lain?

Saya telah menulis satu ton kode LINQ-to-objek dan saya berpendapat bahwa setidaknya dalam domain itu ada baiknya untuk memahami kedua sintaks untuk menggunakan mana saja yang membuat kode lebih sederhana - yang tidak selalu dot-sintaks.

Tentu saja ada saat-saat ketika sintaksis ADALAH cara untuk pergi - yang lain telah menyediakan beberapa kasus ini; namun, saya pikir pemahamannya telah diubah - diberikan rap yang buruk, jika Anda mau. Jadi saya akan memberikan sampel di mana saya percaya pemahaman berguna.

Inilah solusi untuk puzzle substitusi digit: (solusi ditulis menggunakan LINQPad, tetapi dapat berdiri sendiri di aplikasi konsol)

// NO
// NO
// NO
//+NO
//===
// OK

var solutions =
    from O in Enumerable.Range(1, 8) // 1-9
                    //.AsQueryable()
    from N in Enumerable.Range(1, 8) // 1-9
    where O != N
    let NO = 10 * N + O
    let product = 4 * NO
    where product < 100
    let K = product % 10
    where K != O && K != N && product / 10 == O
    select new { N, O, K };

foreach(var i in solutions)
{
    Console.WriteLine("N = {0}, O = {1}, K = {2}", i.N, i.O, i.K);
}

//Console.WriteLine("\nsolution expression tree\n" + solutions.Expression);

... yang keluaran:

N = 1, O = 6, K = 4

Tidak terlalu buruk, logika mengalir secara linier dan kita dapat melihat bahwa ia muncul dengan solusi tunggal yang benar. Teka-teki ini cukup mudah untuk diselesaikan dengan tangan: dengan alasan bahwa 3>> N0, dan O> 4 * N menyiratkan 8> = O> = 4. Itu berarti ada maksimal 10 kasus untuk diuji dengan tangan (2 untuk-oleh- N5 untuk O). Saya sudah cukup tersesat - puzzle ini ditawarkan untuk tujuan ilustrasi LINQ.

Transformasi Kompiler

Ada banyak yang dilakukan kompiler untuk menerjemahkannya ke dalam sintaks dot-ekuivalen. Selain klausa kedua dan selanjutnya yangfromSelectMany biasa diubah menjadi panggilan, kami memiliki letklausa yang menjadi Selectpanggilan dengan proyeksi, keduanya menggunakan pengidentifikasi transparan . Seperti yang akan saya tunjukkan, harus menyebutkan nama pengidentifikasi ini dalam dot-sintaks menghilangkan dari keterbacaan pendekatan itu.

Saya punya trik untuk mengekspos apa yang dilakukan kompiler dalam menerjemahkan kode ini ke sintaks dot. Jika Anda menghapus komentar dari dua baris yang dikomentari di atas dan menjalankannya lagi, Anda akan mendapatkan output berikut:

N = 1, O = 6, K = 4

pohon solusi ekspresi System.Linq.Enumerable + d_ b8.SelectMany (O => Range (1, 8), (O, N) => baru <> f _AnonymousType0 2(O = O, N = N)).Where(<>h__TransparentIdentifier0 => (<>h__TransparentIdentifier0.O != <>h__TransparentIdentifier0.N)).Select(<>h__TransparentIdentifier0 => new <>f__AnonymousType12 (<> h_ TransparentIdentifier0 = <> h _TransparentIdentifier0, NO = ((10 * <> h_ TransparentIdentifier0.N) + <> h _TransparentIdentifier0.O))). Pilih (<> h_ TransparentIdentifier1 => baru <> f _AnonymousType2 2(<>h__TransparentIdentifier1 = <>h__TransparentIdentifier1, product = (4 * <>h__TransparentIdentifier1.NO))).Where(<>h__TransparentIdentifier2 => (<>h__TransparentIdentifier2.product < 100)).Select(<>h__TransparentIdentifier2 => new <>f__AnonymousType32 (<> h_ TransparentIdentifier2 = <> h _TransparentIdentifier2, K = ( <> h_ TransparentIdentifier2.product% 10))). Di mana (<> h _TransparentIdentifier3 => (((<<h_ TransparentIdentifier3.K! = <> h _TransparentIdentifier3. <> h_ TransparentIdentifier2. <>h _TransparentIdentifier1. <> h_TransparentIdentifier0.O) AndAlso (<> h _TransparentIdentifier3.K! = <> H_ TransparentIdentifier3. <> H _TransparentIdentifier2. <> H_TransparentIdentifier1 . <> H _TransparentIdentifier0.N)) DanAlso ((<html) Transparansi . produk / 10) == <> h_ TransparentIdentifier3. <> h _TransparentIdentifier2. <> h_ TransparentIdentifier1. <> h _TransparentIdentifier0.O))). Pilih (<> h_ TransparentIdentifier3 => new <> f _AnonymousType4`3 (N = < > h_ TransparentIdentifier3. <> h _TransparentIdentifier2. <> h_ TransparentIdentifier1. <> h _TransparentIdentifier0.N,O = <> h_ TransparentIdentifier3. <> H_TransparentIdentifier2. <> H_ TransparentIdentifier1. <> H _TransparentIdentifier0.O, K = <> h__TransparentIdentifier3.K))

Menempatkan setiap operator LINQ pada baris baru, menerjemahkan pengidentifikasi "tak terkatakan" ke yang kita bisa "berbicara", mengubah tipe anonim ke bentuk yang mereka kenal dan mengubah AndAlsoistilah pohon ekspresi untuk &&memaparkan transformasi yang dilakukan kompiler untuk sampai pada kesetaraan dalam sintaksis titik:

var solutions = 
    Enumerable.Range(1,8) // from O in Enumerable.Range(1,8)
        .SelectMany(O => Enumerable.Range(1, 8), (O, N) => new { O = O, N = N }) // from N in Enumerable.Range(1,8)
        .Where(temp0 => temp0.O != temp0.N) // where O != N
        .Select(temp0 => new { temp0 = temp0, NO = 10 * temp0.N + temp0.O }) // let NO = 10 * N + O
        .Select(temp1 => new { temp1 = temp1, product = 4 * temp1.NO }) // let product = 4 * NO
        .Where(temp2 => temp2.product < 100) // where product < 100
        .Select(temp2 => new { temp2 = temp2, K = temp2.product % 10 }) // let K = product % 10
        .Where(temp3 => temp3.K != temp3.temp2.temp1.temp0.O && temp3.K != temp3.temp2.temp1.temp0.N && temp3.temp2.product / 10 == temp3.temp2.temp1.temp0.O)
        // where K != O && K != N && product / 10 == O
        .Select(temp3 => new { N = temp3.temp2.temp1.temp0.N, O = temp3.temp2.temp1.temp0.O, K = temp3.K });
        // select new { N, O, K };

foreach(var i in solutions)
{
    Console.WriteLine("N = {0}, O = {1}, K = {2}", i.N, i.O, i.K);
}

Yang jika Anda jalankan Anda dapat memverifikasi bahwa itu lagi menghasilkan:

N = 1, O = 6, K = 4

... tetapi apakah Anda pernah menulis kode seperti ini?

Saya bertaruh jawabannya adalah NONBHN (Tidak Hanya Tidak, Tapi Tidak!) - karena terlalu rumit. Tentu Anda dapat membuat beberapa nama pengidentifikasi yang lebih bermakna daripada "temp0" .. "temp3", tetapi intinya adalah mereka tidak menambahkan apa pun pada kode - mereka tidak membuat kode bekerja lebih baik, mereka tidak membuat kode lebih baik dibaca, mereka hanya jelek kode, dan jika Anda melakukannya dengan tangan, tidak diragukan lagi Anda akan mengacaukannya satu atau tiga waktu sebelum memperbaikinya. Juga, bermain "permainan nama" cukup sulit untuk pengidentifikasi yang bermakna, jadi saya menyambut baik istirahat dari permainan nama yang diberikan kompiler kepada saya dalam pemahaman kueri.

Sampel puzzle ini mungkin tidak cukup nyata bagi Anda untuk dianggap serius; Namun, skenario lain memang ada di mana pemahaman kueri bersinar:

  • Kompleksitas Joindan GroupJoin: pelingkupan variabel rentang dalam joinklausa pemahaman kueri mengubah kesalahan yang mungkin dikompilasi dalam sintaksis titik menjadi kesalahan waktu kompilasi dalam sintaksis pemahaman.
  • Setiap kali kompiler akan memperkenalkan pengidentifikasi transparan dalam transformasi pemahaman, pemahaman menjadi bermanfaat. Ini termasuk penggunaan salah satu dari yang berikut: beberapa fromklausa, join& join..intoklausa dan letklausa.

Saya tahu lebih dari satu toko teknik di kota asal saya yang telah melarang sintaksis pemahaman. Saya pikir ini sangat disayangkan karena sintaks pemahaman hanyalah alat dan yang berguna pada saat itu. Saya pikir itu seperti mengatakan, "Ada hal-hal yang dapat Anda lakukan dengan obeng yang tidak dapat Anda lakukan dengan pahat. Karena Anda dapat menggunakan obeng sebagai pahat, pahat dilarang untuk selanjutnya di bawah keputusan raja."

devgeezer
sumber
-1: Wow. OP sedang mencari sedikit saran. Anda membuat novel! Maukah Anda sedikit memperketat ini?
Jim G.
8

Saran saya adalah menggunakan sintaks pemahaman kueri ketika seluruh ekspresi dapat dilakukan dalam sintaksis pemahaman. Yaitu, saya lebih suka:

var query = from c in customers orderby c.Name select c.Address;

untuk

var query = customers.OrderBy(c=>c.Name).Select(c=>c.Address);

Tapi saya lebih suka

int count = customers.Where(c=>c.City == "London").Count();

untuk

int count = (from c in customers where c.City == "London" select c).Count();

Saya berharap kami telah membuat beberapa sintaks yang membuatnya lebih baik untuk menggabungkan keduanya. Sesuatu seperti:

int count = from c in customers 
            where c.City == "London" 
            select c 
            continue with Count();

Tapi sayangnya kami tidak melakukannya.

Tapi pada dasarnya, ini masalah preferensi. Lakukan yang terlihat lebih baik untuk Anda dan rekan kerja Anda.

Eric Lippert
sumber
3
Atau Anda dapat mempertimbangkan memisahkan pemahaman dari panggilan operator LINQ lainnya melalui refactoring "perkenalkan variabel". misalnya,var londonCustomers = from c in ...; int count = londonCustomers.Count();
devgeezer
3

Seperti SQL adalah cara yang baik untuk memulai. Tetapi karena terbatas (hanya mendukung konstruksi yang didukung bahasa Anda saat ini), akhirnya pengembang beralih ke gaya metode ekstensi.

Saya ingin mencatat bahwa ada beberapa kasus yang dapat dengan mudah diimplementasikan dengan gaya seperti SQL.

Anda juga dapat menggabungkan kedua cara dalam satu permintaan.

SiberianGuy
sumber
2

Saya cenderung menggunakan sintaksis non-kueri kecuali saya perlu mendefinisikan variabel di tengah jalan seperti kueri

from x in list
let y = x.DoExpensiveCalulation()
where y > 42
select y

tapi saya menulis sintaks non-query seperti

x.Where(c => filter)
 .Select(c => datatransform)

sumber
2

Saya selalu menggunakan fungsi ekstensi karena pemesanan. Ambil contoh sederhana Anda - dalam SQL, Anda menulis pilih pertama - meskipun sebenarnya, di mana dulu dieksekusi. Ketika Anda menulis menggunakan metode ekstensi, maka saya merasa jauh lebih terkendali. Saya mendapatkan Intellisense tentang apa yang ditawarkan, saya menulis beberapa hal sesuai urutannya.

DeadMG
sumber
Saya pikir Anda akan menemukan bahwa dalam sintaks "kueri pemahaman" pemesanan pada halaman adalah sama dengan urutan operasi yang terjadi. LINQ tidak mengutamakan "pilih", tidak seperti SQL.
Eric Lippert
1

Saya suka fungsi ekstensi juga.

Mungkin karena itu kurang lompatan sintaks dalam pikiran saya.

Rasanya lebih mudah dibaca oleh mata, terutama jika Anda menggunakan kerangka kerja pihak ketiga yang memiliki linq api.

Erion
sumber
0

Inilah heuristik yang saya ikuti:

Mendukung ekspresi LINQ daripada lambda saat Anda bergabung.

Saya pikir lambda dengan gabungan terlihat berantakan dan sulit dibaca.

Jim G.
sumber