C # Список Понимания = Чистый Синтаксический Сахар? - PullRequest
11 голосов
/ 05 июня 2009

Рассмотрим следующий код C #:

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

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

Ответы [ 4 ]

14 голосов
/ 05 июня 2009

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

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

Обратите внимание, что лямбда будет преобразована в вызов функции, который выполняется для каждого элемента. Это, вероятно, самая большая часть накладных расходов. Я выполнил тест, который показывает, что LINQ примерно в 3 раза медленнее (mono gmcs version 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

РЕДАКТИРОВАТЬ: Gishu сообщает, что VS2008 и Framework 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 - тест производительности ) подтверждают это.

5 голосов
/ 05 июня 2009

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

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

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

4 голосов
/ 17 ноября 2009

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

Но LINQ поддерживает разные типы данных. Например, если у вас есть таблица БД с именем 'numbers', 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, а гораздо более сложная система для «компиляции» запросов. Для получения дополнительной информации выполните поиск Linq Provider. Это такие компоненты, как linq-to-objects и linq-to-xml, и вы можете написать свой собственный, если вы чувствуете себя смелым.

1 голос
/ 05 июня 2009

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

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

...