Налаштування стилю LINQ [закрито]


21

Я прийшов використовувати LINQ у своєму щоденному багато програмуванні. Насправді я рідко, якщо взагалі, використовую явний цикл. Однак я виявив, що більше не використовую синтаксис типу SQL. Я просто використовую функції розширення. Тож тоді сказати:

from x in y select datatransform where filter 

Я використовую:

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

Якому стилю LINQ ви надаєте перевагу та чим подобаються інші у вашій команді?


5
Можливо, варто відзначити, що офіційна позиція MS полягає в тому, що синтаксис запитів є кращим.
R0MANARMY

1
Зрештою це не має значення. Важливо те, що код зрозумілий. Одна форма може бути кращою в одному випадку, інша - в іншому. Тож використовуйте те, що колись підходить.
ChrisF

Я вважаю, що ваш другий приклад називається лямбда-синтаксисом, яким я користуюсь 95% часу. Інші 5% я використовую синтаксис запитів, який є, коли я приєднуюсь, я намагаюся перейти до синтаксису лямбда, але як і інші вказали, він стає безладним.
The Muffin Man

Відповіді:


26

Мені прикро, що позиція Microsoft щодо документації MSDN полягає в тому, що синтаксис запитів є кращим, оскільки я його ніколи не використовую, але весь час використовую синтаксис методу LINQ. Мені подобається, що я можу запускати запити на одне вкладиш до змісту мого серця. Порівняйте:

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

До:

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

Швидше, менше ліній, і на мої очі виглядає чистіше. Синтаксис запитів також не підтримує всіх стандартних операторів LINQ. Приклад запиту, який я нещодавно робив, виглядав приблизно так:

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

Наскільки мені відомо, для копіювання цього запиту, використовуючи синтаксис запиту (наскільки це можливо), це виглядатиме так:

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

Мені це не виглядає легше для читання, і вам все одно потрібно знати, як використовувати синтаксис методу. Особисто я дуже захоплений декларативним стилем, який LINQ робить можливим і використовую його в будь-якій ситуації, коли це взагалі можливо - можливо, іноді на шкоду мені. У конкретному випадку, з синтаксисом методу я можу зробити щось подібне:

// 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)
     });

Я думаю, що вищезгаданий код буде важко зрозуміти тому, хто входить у проект без гарної документації, і якщо у них немає надійного досвіду в LINQ, він все одно може не зрозуміти цього. Тим не менш, синтаксис методу виявляє досить потужну здатність швидко (з точки зору рядків коду) проектувати запит, щоб отримати сукупну інформацію про декілька колекцій, яка в іншому випадку потребує багато втомливого циклічного передбачення. У такому випадку синтаксис методу є надто компактним для того, що ви отримуєте з нього. Спроба зробити це за допомогою синтаксису запитів може отримати непростий досвід досить швидко.


У ролях, який ви можете зробити всередині вибору, але, на жаль, ви не можете вказати, щоб взяти топ X записів, не вдаючись до використання методів LINQ. Це особливо дратує в місцях, де ви знаєте, що вам потрібен лише один запис, і ви повинні поставити всі запити в дужках.
Ziv

2
Тільки для запису ви можете зробити Select (x => x.ItemInfo) .OfType <GeneralMerchInfo> () замість де (). Виберіть (). Cast <> (), який я вважаю, що швидше (великий O 2n замість n * 2м я думаю). Але ви абсолютно праві, лямбда-синтаксис набагато кращий з точки зору читабельності.
Ед Джеймс

16

Я вважаю функціональний синтаксис більш приємним для очей. Єдиний виняток - якщо мені потрібно приєднати більше двох наборів. Join () дуже швидко збуває з розуму.


Погоджено ... Я набагато більше віддаю перевагу вигляду та читабельності від методів розширення, за винятком (як зазначено) під час приєднання. Постачальники компонентів (наприклад, Telerik) дуже багато використовують методи розширення. Приклад, про який я думаю, - це їх управління Rad в ASP.NET MVC. Вам потрібно бути дуже досвідченим, використовуючи методи розширення, щоб використовувати / читати їх.
Catchops

Прийшов сказати це. Зазвичай я використовую лямбда, якщо немає участі в об'єднанні. Після того, як є з'єднання, синтаксис LINQ, як правило, є більш читабельним.
Шон

10

Чи не пізно додати ще одну відповідь?

Я написав тонну коду LINQ до об’єктів, і я стверджую, що принаймні в цьому домені добре розуміти обидва синтаксиси, щоб використовувати те, що робиться для більш простого коду - що не завжди є кращим синтаксисом.

Звичайно, бувають випадки, коли синтаксис крапок IS є шляхом - інші навели кілька таких випадків; однак, я думаю, що розуміння були коротко змінені - якщо це буде погано, якщо ви хочете. Тож я надам зразок, де я вважаю, що розуміння корисні.

Ось рішення головоломки з підстановкою цифр: (рішення написане за допомогою LINQPad, але може бути автономним у консольному додатку)

// 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);

... яка виводить:

N = 1, O = 6, K = 4

Не надто погано, логіка протікає лінійно, і ми можемо побачити, що вона виходить з єдиним правильним рішенням. Цю головоломку досить легко розв’язати вручну: міркуючи, що 3>> N0 і O> 4 * N означає 8> = O> = 4. Це означає, що для перевірки вручну потрібно перевірити максимум 10 випадків (2 для N-by- 5 для O). Я досить збився - ця головоломка пропонується для цілей ілюстрації LINQ.

Перетворення компілятора

Компілятор робить багато, щоб перевести це в еквівалентний кращий синтаксис. Окрім звичайних другого та наступних fromпропозицій перетворюються на SelectManyдзвінки, у нас є letпропозиції, які стають Selectвикликами з проекціями, обидва з яких використовують прозорі ідентифікатори . Як я збираюся показати, необхідність називати ці ідентифікатори в синтаксисі крапок забирає читабельність цього підходу.

У мене є хитрість розкрити те, що робить компілятор при перекладі цього коду в кращий синтаксис. Якщо коментувати два коментованих рядка вище та запустити його знову, ви отримаєте такий результат:

N = 1, O = 6, K = 4

Дерево вираження рішення System.Linq.Eumerable + d_ b8.SelectMany (O => Діапазон (1, 8), (O, N) => новий <> 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) + <> ч _TransparentIdentifier0.O))). Виберіть (<> H_ TransparentIdentifier1 => новий <> е _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 = <> ч _TransparentIdentifier2, К = ( <> h_ TransparentIdentifier2.product% 10))). Де (<> h _TransparentIdentifier3 => ((((<<h_ TransparentIdentifier3.K! = <> h _TransparentIdentifier3. <> h_ TransparentIdentifier2. <>h _TransparentIdentifier1. <> h_TransparentIdentifier0.O) AndAlso (<> ч _TransparentIdentifier3.K! = <> H_ TransparentIdentifier3. <> Ч _TransparentIdentifier2. <> H_ TransparentIdentifier1. <> Ч _TransparentIdentifier0.N)) AndAlso ((<> h_ TransparentIdentifier3. <> Ч _TransparentIdentifier2. product / 10) == <> h_ TransparentIdentifier3. <> h _TransparentIdentifier2. <> h_ TransparentIdentifier1. <> h _TransparentIdentifier0.O))) Виберіть (<> h_ TransparentIdentifier3 => новий <> 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))

Поклавши кожного оператора LINQ в новий рядок, переклавши "невимовні" ідентифікатори на ті, які ми можемо "говорити", змінивши анонімні типи на їхню звичну форму та змінивши AndAlsoлінгво дерева виразів, щоб &&викрити перетворення, які компілятор робить для отримання еквівалента у крапці-синтаксисі:

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);
}

У разі запуску ви можете переконатися, що він знову видає:

N = 1, O = 6, K = 4

... але ти коли-небудь писав би такий код?

Я б став до відповіді: NONBHN (не тільки ні, але пекло ні!) - тому що це занадто складно. Звичайно, ви можете придумати більш значущі імена ідентифікаторів, ніж "temp0" .. "temp3", але справа в тому, що вони нічого не додають до коду - вони не роблять код ефективнішим, вони не роблять зробіть код читати краще, вони лише потворно знімають код, і якби ви це робили вручну, без сумніву, ви зіпсуєте його час або три, перш ніж правильно це зробити. Крім того, грати в «гру з іменами» досить складно для значущих ідентифікаторів, тому я вітаю перерву від гри з іменами, яку компілятор надає мені в розумінні запитів.

Цей зразок головоломки може бути недостатньо реальним , щоб ви сприйняли його серйозно; однак існують інші сценарії, де світяться розуміння запитів:

  • Складність Joinта GroupJoin: розміщення змінних діапазону в joinзастереженнях про розуміння запитів перетворюють помилки, які в іншому випадку можуть скластись у крапці-синтаксисі, у помилки часу компіляції у синтаксисі розуміння.
  • Кожного разу, коли компілятор вводить прозорий ідентифікатор у трансформацію розуміння, розуміння стає доцільним. Сюди входить використання будь-якого з наступного: кілька fromпропозицій, join& join..intoпунктів і letзастережень.

Я знаю про більше ніж один інженерний цех у моєму рідному місті, який заборонив синтаксис розуміння. Я думаю, що це шкода, оскільки синтаксис розуміння - це лише інструмент і корисний при цьому. Я думаю, що це дуже схоже на те, що говорити: "Є речі, які можна зробити за допомогою викрутки, яку неможливо зробити із стамескою. Оскільки ви можете використовувати викрутку як зубило, долота забороняються відтепер указом короля".


-1: Нічого собі ОП шукала невелику пораду. Ти викрутив роман! Ви не хотіли б трохи посилити це?
Джим Г.

8

Моя порада - використовувати синтаксис розуміння запиту, коли весь вираз можна виконати в синтаксисі розуміння. Тобто я вважаю за краще:

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

до

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

Але я вважаю за краще

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

до

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

Я б хотів, щоб ми придумали якийсь синтаксис, який зробив би приємніше змішати це два. Щось на зразок:

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

Але, на жаль, ми цього не зробили.

Але в основному це питання переваги. Зробіть той, який виглядає краще для вас та ваших колег.


3
Крім того, ви можете розглянути питання про відокремлення розуміння від інших викликів оператора LINQ через рефакторинг "ввести пояснювальну змінну". наприклад,var londonCustomers = from c in ...; int count = londonCustomers.Count();
devgeezer

3

SQL-подібний - це хороший спосіб почати. Але оскільки він обмежений (він підтримує лише ті конструкції, які підтримує поточна мова), врешті розробники переходять до стилю методів розширення.

Я хотів би зазначити, що є деякі випадки, які можуть бути легко реалізовані у стилі, подібному до SQL.

Також ви можете комбінувати обидва способи в одному запиті.


2

Я схильний використовувати синтаксис без запитів, якщо мені не потрібно визначати змінну середину шляху, хоч запит, як

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

але я записую синтаксис без запитів, як

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

2

Я завжди використовую функції розширення через замовлення. Візьміть ваш простий приклад - у SQL ви написали select first-, хоча насправді, де виконувались першими. Коли ти пишеш, використовуючи методи розширення, то я відчуваю набагато більше контролю. Я отримую від Intellisense про те, що пропонується, я записую речі в тому порядку, як вони трапляються.


Я думаю, ви побачите, що в синтаксисі "розуміння запитів" впорядкування на сторінці таке ж, як і порядок, в якому відбуваються операції. LINQ спочатку не ставить "select" на відміну від SQL.
Ерік Ліпперт

1

Мені також подобається функція розширення.

Можливо, причина цього - менший стрибок синтаксису в моїй свідомості.

Він також відчутний для очей, особливо якщо ви використовуєте сторонні рамки, у яких є linq api.


0

Ось евристика, яку я слідую:

Улюблені вирази LINQ над лямбдами, коли ви приєднуєтесь.

Я думаю, що лямбда з приєднаннями виглядають безладно і їх важко читати.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.