Поделиться через


Промежуточная материализация

Если не соблюдать осторожность, то в некоторых ситуациях может происходить преждевременная материализация коллекций в запросах, что приводит к радикальному изменению характера использования памяти и снижению производительности приложения. Некоторые стандартные операторы запросов материализуют свою исходную коллекцию еще до получения хотя бы одного элемента. Например, при выполнении оператора Enumerable.OrderBy вначале происходит итерация по всей исходной коллекции, затем сортировка всех элементов и лишь после этого осуществляется возврат первого элемента. Это означает, что самой дорогостоящей операцией является получение первого элемента упорядоченной коллекции, после чего затраты ресурсов на каждый последующий элемент становятся меньше. Это вполне оправдано, поскольку функционирование данного оператора запроса не может быть организовано иначе.

Пример

В этом примере внесены изменения по сравнению с предыдущим примером. В методе AppendString осуществляется вызов ToList<TSource> до начала итераций по данным источника. Это становится причиной материализации.

Примечание

В следующем примере используется конструкция yield return языка C#.Так как в Visual Basic 2008 эквивалентной функции нет, этот пример приведен только на C#.

public static class LocalExtensions
{
    public static IEnumerable<string>
      ConvertCollectionToUpperCase(this IEnumerable<string> source)
    {
        foreach (string str in source)
        {
            Console.WriteLine("ToUpper: source >{0}<", str);
            yield return str.ToUpper();
        }
    }

    public static IEnumerable<string>
      AppendString(this IEnumerable<string> source, string stringToAppend)
    {
        // the following statement materializes the source collection in a List<T>
        // before iterating through it
        foreach (string str in source.ToList())
        {
            Console.WriteLine("AppendString: source >{0}<", str);
            yield return str + stringToAppend;
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        string[] stringArray = { "abc", "def", "ghi" };

        IEnumerable<string> q1 =
            from s in stringArray.ConvertCollectionToUpperCase()
            select s;

        IEnumerable<string> q2 =
            from s in q1.AppendString("!!!")
            select s;

        foreach (string str in q2)
        {
            Console.WriteLine("Main: str >{0}<", str);
            Console.WriteLine();
        }
    }
}

Этот пример выводит следующие данные.

ToUpper: source >abc<
ToUpper: source >def<
ToUpper: source >ghi<
AppendString: source >ABC<
Main: str >ABC!!!<

AppendString: source >DEF<
Main: str >DEF!!!<

AppendString: source >GHI<
Main: str >GHI!!!<

В этом примере можно видеть, что применение вызова ToList<TSource> вынуждает метод AppendString сформировать перечисление по всему источнику, прежде чем возвратить первый элемент. Если источник представляет собой большой массив, то в результате характер использования памяти приложением существенно изменится.

Следующие шаги

Стандартные операторы запроса можно также соединять в виде цепочки. Это показано в последнем разделе учебника.

См. также

Основные понятия

Учебник. Объединение запросов в цепочки