11

Рассмотрим следующий C# код:C# List Comprehensions = чистый синтаксический сахар?

IEnumerable numbers = Enumerable.Range(0, 10); 
var evens = from num in numbers where num % 2 == 0 select num; 

Является ли это чисто синтаксический сахар, чтобы позволить мне написать for или foreach петлю в качестве однострочника? Существуют ли какие-либо оптимизаторы компилятора под обложками, которые делают понимание списков более эффективным, чем конструкция цикла? Как это работает под капотом?

ответ

14

Как сказал Джейсон, ваш код эквивалентен:

Enumerable.Range(0, 10).Where(n => n % 2 == 0); 

Примечание лямбда будет преобразован в вызов функции, которая делается для каждого элемента. Это, вероятно, самая большая часть накладных расходов. Я сделал тест, который показывает LINQ примерно в 3 раза медленнее (моно GMCS версия 1.2.6.0) на этой точной задачи

 
    Time for 10000000 for loop reps: 00:00:17.6852560 
    Time for 10000000 LINQ reps: 00:00:59.0574430 

    Time for 1000000 for loop reps: 00:00:01.7671640 
    Time for 1000000 LINQ reps: 00:00:05.8868350 

EDIT: Gishu сообщает, что VS2008 и рамки v3.5 SP1 дает:

 
    Time for 1000000 loop reps: :00.3724585 
    Time for 1000000 LINQ reps: :00.5119530 

LINQ примерно в 1,4 раза медленнее.

Он сравнивает цикл for и список LINQ (и любую структуру, которую он использует внутри). В любом случае, он преобразует результат в массив (необходимо, чтобы заставить LINQ перестать быть «ленивым»). Обе версии Повторяют:

using System; 
using System.Collections.Generic; 
using System.Diagnostics; 
using System.Linq; 

public class Evens 
{ 
    private static readonly int[] numbers = new int[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; 

    private static int MAX_REPS = 1000000; 

    public static void Main() 
    { 
     Stopwatch watch = new Stopwatch(); 

     watch.Start(); 
     for(int reps = 0; reps < MAX_REPS; reps++) 
     { 
      List<int> list = new List<int>(); // This could be optimized with a default size, but we'll skip that. 
      for(int i = 0; i < numbers.Length; i++) 
      { 
       int number = numbers[i]; 
       if(number % 2 == 0) 
        list.Add(number); 
      } 
      int[] evensArray = list.ToArray(); 
     } 
     watch.Stop(); 
     Console.WriteLine("Time for {0} for loop reps: {1}", MAX_REPS, watch.Elapsed); 

     watch.Reset(); 
     watch.Start(); 
     for(int reps = 0; reps < MAX_REPS; reps++) 
     { 
      var evens = from num in numbers where num % 2 == 0 select num; 
      int[] evensArray = evens.ToArray(); 
     } 
     watch.Stop(); 
     Console.WriteLine("Time for {0} LINQ reps: {1}", MAX_REPS, watch.Elapsed); 
    } 
} 

Прошлых тесты производительности на аналогичных задачах (например, LINQ vs Loop - A performance test) подтверждает это.

+0

Вы используете это на Mono? Вы уверены, что это сопоставимо с Microsoft IL? –

+2

Mono использует MSIL, который также известен как CIL после стандартизации. –

+2

Да, но это не значит, что два компилятора создают эквивалентный вывод. –

5

Вы можете упростить код далее

var evens = Enumerable.Range(0, 10).Where(n => n % 2 == 0); 

Одним из преимуществ этой формы является то, что выполнение этого выражения откладывается до evens не повторяется в течение (foreach(var n in evens) { ... }). Вышеприведенный оператор просто сообщает компилятору понять, как перечислить четные числа от 0 до 10, но не выполнять эту идею до тех пор, пока это не будет абсолютно необходимо.

1

В вашем коде выше у вас есть запрос Linq, который циклически перебирает IEnumerable так же, как и цикл foreach. Тем не менее, в вашем коде есть LOT, идущий под капотом. Для foreach, вероятно, намного эффективнее, если вы намерены написать высокопроизводительный цикл. Linq предназначен для другой цели (обобщенный доступ к данным).

Интерфейс IEnumerable предоставляет метод итератора, который затем непрерывно вызывается конструкцией цикла, как запрос foreach или Linq. Итератор возвращает следующий элемент в коллекции каждый раз, когда он вызывается.

+0

By 'for' loop Я имел в виду как' for', так и 'foreach' ... Я редактировал вопрос, чтобы отразить это ... –

+0

Jonas, я обновил свой ответ, чтобы отразить ваши комментарии. –

+0

ОК, но скажем, что вместо простого выполнения примера, подобного приведенному выше, у меня есть сценарий, который больше похож на этот: у меня есть список , и я хочу отфильтровать все Person.LastName, которое соответствует определенному регулярному выражению. Я использую синтаксис List Comprehension для итерации по списку , и я передаю регулярное выражение как выражение лямбда. Будет ли это более эффективным, чем писать цикл? –

4

LINQ работает по-разному для разных типов данных. Вы кормите его объектами, поэтому использует LINQ-to-objects. Это преобразуется в код, похожий на простой for-loop.

Но LINQ поддерживает различные типы данных.Например, если у вас была таблица db с именем «числа», LINQ-to-SQL переведет тот же запрос;

var evens = from num in numbers where num % 2 == 0 select num; 

в SQL как это;

select num from numbers where num % 2 = 0 

, а затем выполняет это. Обратите внимание, что фильтрация не выполняется путем создания цикла for с внутренним блоком if; предложение 'where' изменяет запрос, отправленный на сервер базы данных. Перевод от запроса к исполняемому коду зависит от типа данных, которые вы его подаете.

Таким образом, LINQ не является синтаксическим сахаром для for-loops, а гораздо более привлекательной системой для «компиляции» запросов. Для получения дополнительной информации, найдите Linq Provider. Это такие компоненты, как linq-to-objects и linq-to-xml, и вы можете написать свое собственное, если вы чувствуете себя храбрым.

Смежные вопросы