Настройки стиля LINQ [закрыто]

21

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

from x in y select datatransform where filter 

Я использую:

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

Какой стиль LINQ вы предпочитаете, и кому из вашей команды это удобно?

Erin
источник
5
Возможно, стоит отметить, что официальная позиция MS заключается в том, что синтаксис запроса предпочтительнее.
R0MANARMY
1
В конечном итоге это не имеет значения. Важно то, что код понятен. Одна форма может быть лучше в одном случае, другая в другом случае. Так что используйте то, что всегда уместно в то время.
ChrisF
Я полагаю, что ваш второй пример называется лямбда-синтаксисом, который я использую в 95% случаев. Остальные 5% я использую синтаксис запроса, когда я делаю соединения, я пытаюсь перейти к лямбда-синтаксическим соединениям, но, как и другие отмечали, это становится грязным.
Человек

Ответы:

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, они все равно могут его не понять. Тем не менее, синтаксис метода предоставляет довольно мощную возможность быстро (с точки зрения строк кода) проецировать запрос, чтобы получить сводную информацию о нескольких коллекциях, которые в противном случае потребовали бы много утомительного цикла foreach. В таком случае синтаксис метода очень компактен для того, что вы получаете из него. Попытка сделать это с помощью синтаксиса запроса может быть довольно громоздкой.

klir2m
источник
Приведение, которое вы можете выполнить внутри select, но, к сожалению, вы не можете указать, чтобы брать верхние записи X, не прибегая к использованию методов LINQ. Это особенно раздражает в тех местах, где вы знаете, что вам нужна только одна запись и все запросы заключаются в квадратные скобки.
Зив
2
Просто для записи вы можете сделать Select (x => x.ItemInfo) .OfType <GeneralMerchInfo> () вместо Where (). Select (). Cast <> (), который, я считаю, быстрее (большой O 2n вместо п * 2м я думаю). Но вы совершенно правы, лямбда-синтаксис намного лучше с точки зрения читабельности.
Эд Джеймс
16

Я нахожу функциональный синтаксис более приятным для глаз. Единственное исключение - если мне нужно объединить более двух комплектов. Join () очень быстро сходит с ума.

Джон Крафт
источник
Согласен ... Я гораздо больше предпочитаю внешний вид и удобочитаемость из методов расширения, за исключением (как указано) при присоединении. Поставщики компонентов (например, Telerik) широко используют методы расширения. Пример, о котором я думаю, это их Rad Controls в ASP.NET MVC. Вы должны быть очень опытными, используя методы расширения, чтобы использовать / читать их.
Catchops
Пришел, чтобы сказать это. Я обычно использую лямбды, если не участвует соединение. После объединения синтаксис LINQ становится более читабельным.
Шон
10

Не слишком ли поздно добавить еще один ответ?

Я написал тонну кода LINQ-to-objects, и я утверждаю, что по крайней мере в этой области хорошо понимать оба синтаксиса, чтобы использовать тот, который создает более простой код - что не всегда является точечным синтаксисом.

Конечно , бывают случаи , когда дот-синтаксис 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.Enumerable + d_ b8.SelectMany (O => Range (1, 8), (O, N) => new <> 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))). Выберите (<> h_ TransparentIdentifier1 => new <> 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))). Где (<> 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)) AndAlso ((<> h_ TransparentIdentifier . product / 10) == <> h_ TransparentIdentifier3. <> h _TransparentIdentifier2. <> h_ TransparentIdentifier1. <> h _TransparentIdentifier0.O))). Выберите (<> 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))

Помещая каждый оператор 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

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

Держу пари, что ответ НОНБН (не только нет, но, черт возьми, нет!), Потому что это слишком сложно. Конечно, вы можете придумать более значимые имена идентификаторов, чем «temp0» .. «temp3», но дело в том, что они ничего не добавляют в код - они не делают код лучше, они не делают сделать код лучше читаемым, он только уродлив код, и если вы делали это вручную, без сомнения, вы бы испортили его раз или три, прежде чем сделать это правильно. Кроме того, играть в «игру с именем» достаточно сложно для значимых идентификаторов, поэтому я приветствую перерыв в игре с именами, которую компилятор предоставляет мне в понимании запросов.

Этот образец головоломки может оказаться недостаточно реальным для вас, чтобы воспринимать его всерьез; Тем не менее, существуют другие сценарии, где понимание запросов блестит:

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

Я знаю больше, чем один инженерный магазин в моем родном городе, где запрещен синтаксис понимания. Я думаю, что это жаль, поскольку синтаксис понимания - всего лишь инструмент и полезный. Я думаю, что это очень похоже на высказывание: «Есть вещи, которые вы можете сделать с помощью отвертки, которые вы не можете сделать с помощью зубила. Поскольку вы можете использовать отвертку в качестве зубила, отныне зубья запрещены по указу короля».

devgeezer
источник
-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.

Также вы можете комбинировать оба способа в одном запросе.

SiberianGuy
источник
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 о том, что предлагается, я пишу вещи в том порядке, в котором они происходят.

DeadMG
источник
Я думаю, вы обнаружите, что в синтаксисе «понимание запросов» порядок на странице такой же, как порядок выполнения операций. LINQ не ставит «select» первым, в отличие от SQL.
Эрик Липперт
1

Мне тоже нравится функция расширения.

Может быть, потому что это меньше, чем прыжок синтаксиса в моей голове.

Это также более читабельно, особенно если вы используете сторонние фреймворки, в которых есть linq api.

Erion
источник
0

Вот эвристика, которой я следую:

Пользуйся выражениями LINQ над лямбдами, когда у тебя есть объединения.

Я думаю, что лямбды с соединениями выглядят грязно и их трудно читать.

Джим Г.
источник