Прочитать на английском

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


Общие сведения о LINQ

LINQ (Language-Integrated Query) предоставляет возможности выполнения запросов на уровне языка и API функции высшего порядка в C# и Visual Basic для написания выразительного декларативного кода.

Синтаксис запросов на основе языка

Это синтаксис запросов на основе языка:

C#
var linqExperts = from p in programmers
                  where p.IsNewToLINQ
                  select new LINQExpert(p);

Это тот же пример, использующий API IEnumerable<T>:

C#
var linqExperts = programmers.Where(p => p.IsNewToLINQ)
                             .Select(p => new LINQExpert(p));

LINQ является выразительной методикой

Предположим, что имеющийся список домашних животных нужно преобразовать в словарь, чтобы находить питомца напрямую по его значению RFID.

Это традиционный императивный код:

C#
var petLookup = new Dictionary<int, Pet>();

foreach (var pet in pets)
{
    petLookup.Add(pet.RFID, pet);
}

Цель написания кода заключается не только в создании нового Dictionary<int, Pet> и его добавления с помощью цикла, но также в преобразовании существующего списка в словарь! LINQ позволяет выполнить эту задачу, тогда как принудительный код — нет.

Это эквивалентное выражение LINQ:

C#
var petLookup = pets.ToDictionary(pet => pet.RFID);

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

Поставщики LINQ упрощают доступ к данным

Использование значительной части существующего ПО связано с обработкой данных из определенного источника (баз данных, JSON, XML и т. д.). Часто для этого требуется изучать новый API по каждому источнику данных, что может оказаться раздражающим фактором. LINQ упрощает эту задачу путем абстрагирования общих элементов доступа к данным в синтаксис запросов, который имеет один и тот же вид независимо от выбираемого источника данных.

При этом будут найдены все элементы XML с указанным значением атрибута:

C#
public static IEnumerable<XElement> FindAllElementsWithAttribute(XElement documentRoot, string elementName,
                                           string attributeName, string value)
{
    return from el in documentRoot.Elements(elementName)
           where (string)el.Element(attributeName) == value
           select el;
}

Написать код для просмотра XML-документа вручную будет намного сложнее.

Поставщики LINQ можно использовать для реализации целого ряда задач, не ограничиваясь только взаимодействием с XML. LINQ to SQL является довольно минималистичным инструментом объектно-реляционного сопоставления (ORM) для базы данных сервера MSSQL. Библиотека Json.NET предоставляет эффективные возможности просмотра документов JSON с помощью LINQ. Кроме того, если библиотека с необходимыми вам функциями отсутствует, можно написать собственный поставщик LINQ!

Причины использования синтаксиса запроса

Зачем использовать синтаксис запроса? Этот вопрос возникает довольно часто. В конце концов, следующий код:

C#
var filteredItems = myItems.Where(item => item.Foo);

гораздо более лаконичен, чем этот:

C#
var filteredItems = from item in myItems
                    where item.Foo
                    select item;

Может быть, синтаксис API просто является самым кратким способом формирования синтаксиса запроса?

Нет. Синтаксис запроса позволяет использовать предложение let, которое дает возможность ввести и привязать переменную в области выражения и применять ее в последующих частях выражения. Можно воспроизвести тот же код только с помощью синтаксиса API, но, скорее всего, этот код будет трудночитаемым.

Поэтому возникает вопрос о том, можно ли просто использовать синтаксис запросов?

Ответом будет Да, если...

  • в существующей базе кода уже используется синтаксис запроса;
  • необходимо ограничить переменные в запросах из-за сложности;
  • вы предпочитаете синтаксис запросов, который не отвлекает внимание от базы кода.

Ответом будет Нет, если...

  • в существующей базе кода уже используется синтаксис API;
  • нет необходимости ограничивать переменные в запросах;
  • вы предпочитаете использовать синтаксис API, который не отвлекает внимание от базы кода.

Важные части LINQ

Полный список примеров LINQ см. на странице 101 LINQ Samples (101 пример LINQ).

Далее приводятся примеры демонстрации некоторых важных частей LINQ. Она не является исчерпывающей, так как LINQ предоставляет больше возможностей, чем показано здесь.

Совершенно необходимые компоненты — Where, Select и Aggregate:

C#
// Filtering a list.
var germanShepherds = dogs.Where(dog => dog.Breed == DogBreed.GermanShepherd);

// Using the query syntax.
var queryGermanShepherds = from dog in dogs
                          where dog.Breed == DogBreed.GermanShepherd
                          select dog;

// Mapping a list from type A to type B.
var cats = dogs.Select(dog => dog.TurnIntoACat());

// Using the query syntax.
var queryCats = from dog in dogs
                select dog.TurnIntoACat();

// Summing the lengths of a set of strings.
int seed = 0;
int sumOfStrings = strings.Aggregate(seed, (s1, s2) => s1.Length + s2.Length);

Спрямление списка списков:

C#
// Transforms the list of kennels into a list of all their dogs.
var allDogsFromKennels = kennels.SelectMany(kennel => kennel.Dogs);

Объединение двух наборов (с пользовательским блоком сравнения):

C#
public class DogHairLengthComparer : IEqualityComparer<Dog>
{
    public bool Equals(Dog a, Dog b)
    {
        if (a == null && b == null)
        {
            return true;
        }
        else if ((a == null && b != null) ||
                 (a != null && b == null))
        {
            return false;
        }
        else
        {
            return a.HairLengthType == b.HairLengthType;
        }
    }

    public int GetHashCode(Dog d)
    {
        // Default hashcode is enough here, as these are simple objects.
        return d.GetHashCode();
    }
}
...

// Gets all the short-haired dogs between two different kennels.
var allShortHairedDogs = kennel1.Dogs.Union(kennel2.Dogs, new DogHairLengthComparer());

Пересечение двух наборов:

C#
// Gets the volunteers who spend share time with two humane societies.
var volunteers = humaneSociety1.Volunteers.Intersect(humaneSociety2.Volunteers,
                                                     new VolunteerTimeComparer());

Упорядочение

C#
// Get driving directions, ordering by if it's toll-free before estimated driving time.
var results = DirectionsProcessor.GetDirections(start, end)
              .OrderBy(direction => direction.HasNoTolls)
              .ThenBy(direction => direction.EstimatedTime);

Равенство свойств экземпляра

И, наконец, расширенный пример: определение равенства значений свойств двух экземпляров одного типа (взят и изменен на основе этой записи на сайте StackOverflow):

C#
public static bool PublicInstancePropertiesEqual<T>(this T self, T to, params string[] ignore) where T : class
{
    if (self == null || to == null)
    {
        return self == to;
    }

    // Selects the properties which have unequal values into a sequence of those properties.
    var unequalProperties = from property in typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance)
                            where !ignore.Contains(property.Name)
                            let selfValue = property.GetValue(self, null)
                            let toValue = property.GetValue(to, null)
                            where !Equals(selfValue, toValue)
                            select property;
    return !unequalProperties.Any();
}

PLINQ

PLINQ или параллельный LINQ — это механизм параллельного выполнения выражений LINQ. Другими словами, обычное выражение LINQ можно легко распараллелить между любым количеством потоков. Это выполняется путем вызова AsParallel(), предшествующего выражению.

Рассмотрим следующий пример.

C#
public static string GetAllFacebookUserLikesMessage(IEnumerable<FacebookUser> facebookUsers)
{
    var seed = default(UInt64);

    Func<UInt64, UInt64, UInt64> threadAccumulator = (t1, t2) => t1 + t2;
    Func<UInt64, UInt64, UInt64> threadResultAccumulator = (t1, t2) => t1 + t2;
    Func<Uint64, string> resultSelector = total => $"Facebook has {total} likes!";

    return facebookUsers.AsParallel()
                        .Aggregate(seed, threadAccumulator, threadResultAccumulator, resultSelector);
}

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

В виде схемы:

PLINQ diagram

Параллелизуемые задания, использующие ресурсы ЦП, которые можно легко выразить через LINQ (другими словами, чистые функции без побочных эффектов) являются отличным кандидатом для PLINQ. Для работы с заданиями, которые имеют побочный эффект, рекомендуется рассмотреть возможность использования библиотеки параллельных задач.

Дополнительные ресурсы

  • 101 пример по LINQ
  • LINQPad — среда и механизм запросов к базе данных для C#/F#/Visual Basic
  • EduLinq — электронная книга с информацией по реализации LINQ to Objects