0 7.4K ru

Объяснение методов LINQ в .NET с помощью изображений

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

Select

select linq

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

Пример использования Select:

var people = new List<Person>
{
    new Person ("Tom", 23),
    new Person ("Bob", 27),
    new Person ("Sam", 29),
    new Person ("Alice", 24)
};
var names = people.Select(u => u.Name);
 
foreach (string n in names)
     Console.WriteLine(n);

Where

where linq

Where фильтрует набор значений базируясь на Func<TSource,bool> предикате. В данном примере мы хотим получить только зеленые круги. Набор результатов может быть таким же, меньшим или даже пустым.

Пример использования Where

List<string> fruits =
    new List<string> { "apple", "passionfruit", "banana", "mango",
                    "orange", "blueberry", "grape", "strawberry" };

IEnumerable<string> query = fruits.Where(fruit => fruit.Length < 6);

foreach (string fruit in query)
{
    Console.WriteLine(fruit);
}
/*
 This code produces the following output:

 apple
 mango
 grape
*/

SelectMany

selectmany linq

SelectMany используется для выравнивания списков. Если у вас есть список внутри списка, мы можем использовать его, чтобы свести его к одномерному представлению.

Пример использования SelectMany

class PetOwner
{
    public string Name { get; set; }
    public List<string> Pets { get; set; }
}

public static void SelectManyEx2()
{
    PetOwner[] petOwners =
        { new PetOwner { Name="Higa, Sidney",
              Pets = new List<string>{ "Scruffy", "Sam" } },
          new PetOwner { Name="Ashkenazi, Ronen",
              Pets = new List<string>{ "Walker", "Sugar" } },
          new PetOwner { Name="Price, Vernette",
              Pets = new List<string>{ "Scratches", "Diesel" } },
          new PetOwner { Name="Hines, Patrick",
              Pets = new List<string>{ "Dusty" } } };

    // Project the items in the array by appending the index
    // of each PetOwner to each pet's name in that petOwner's
    // array of pets.
    IEnumerable<string> query =
        petOwners.SelectMany((petOwner, index) =>
                                 petOwner.Pets.Select(pet => index + pet));

    foreach (string pet in query)
    {
        Console.WriteLine(pet);
    }
}

// This code produces the following output:
//
// 0Scruffy
// 0Sam
// 1Walker
// 1Sugar
// 2Scratches
// 2Diesel
// 3Dusty

Zip

Zip linq

С помощью Zip мы «объединяем» два списка с помощью заданной функции слияния. Мы объединяем объекты вместе, пока не закончатся объекты в любом из списков. Как видно из примера:  в первом списке 2 элемента, в втором — 3. Следовательно, результирующий набор содержит только 2 элемента.

Пример использования Zip

int[] numbers = { 1, 2, 3, 4 };
string[] words = { "one", "two", "three" };

var numbersAndWords = numbers.Zip(words, (first, second) => first + " " + second);

foreach (var item in numbersAndWords)
    Console.WriteLine(item);

// This code produces the following output:

// 1 one
// 2 two
// 3 three

OrderBy

order by linq

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

Пример использования OrderBy

class Pet
{
    public string Name { get; set; }
    public int Age { get; set; }
}

public static void OrderByEx1()
{
    Pet[] pets = { new Pet { Name="Barley", Age=8 },
                   new Pet { Name="Boots", Age=4 },
                   new Pet { Name="Whiskers", Age=1 } };

    IEnumerable<Pet> query = pets.OrderBy(pet => pet.Age);

    foreach (Pet pet in query)
    {
        Console.WriteLine("{0} - {1}", pet.Name, pet.Age);
    }
}

/*
 This code produces the following output:

 Whiskers - 1
 Boots - 4
 Barley - 8
*/

Distinct

distinct linq explanation

Distinct возвращает новый enumerable, в котором удаляются все дубликаты, что-то похожее на Set. Обратите внимание, что для ссылочного типа по умолчанию проверяется равенство ссылок, что может привести к ложным результатам. Результирующий набор может быть таким же или меньшим.

Пример использования Distinct

List<int> ages = new List<int> { 21, 46, 46, 55, 17, 21, 55, 55 };
IEnumerable<int> distinctAges = ages.Distinct();
Console.WriteLine("Distinct ages:");

foreach (int age in distinctAges)
{
    Console.WriteLine(age);
}

/*
 This code produces the following output:

 Distinct ages:
 21
 46
 55
 17
*/

DistinctBy

DistinctBy linq

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

Пример использования DistinctBy

public record Agent
{
    public string Name { get; init; }
    public byte Age { get; init; }
}
var agents = new[]
{
    new Agent() {Name = "Ethan Hunt", Age = 40},
    new Agent() {Name = "James Bond", Age = 40},
    new Agent() {Name = "Jason Bourne", Age = 35},
    new Agent() {Name = "Evelyn Salt", Age = 30},
    new Agent() {Name = "Jack Ryan", Age = 36},
    new Agent() {Name = "Jane Smith", Age = 35},
    new Agent() {Name = "Oren Ishii", Age = 30},
    new Agent() {Name = "Natasha Romanoff", Age = 33}
};
var distinctlyAgedAgents = agents.DistinctBy(x => x.Age);
foreach (var agent in distinctlyAgedAgents)
{
    Console.WriteLine($"Agent {agent.Name} is {agent.Age}");
}
Agent Ethan Hunt is 40
Agent Jason Bourne is 35
Agent Evelyn Salt is 30
Agent Jack Ryan is 36
Agent Natasha Romanoff is 33

Aggregate

Aggregate linq

Также известен как «reduce». Основная идея состоит в том, чтобы объединить/свести набор входных данных к одному значению. Сумма списка будет примером агрегата. Также яркими примерами могут быть определение среднего / максимального / минимального значения. Он всегда начинается с начального значения, и каждый отдельный элемент в списке агрегируется заданной пользователем функцией сверху.

Пример использования Aggregate

string[] fruits = { "apple", "mango", "orange", "passionfruit", "grape" };

// Determine whether any string in the array is longer than "banana".
string longestName =
    fruits.Aggregate("banana",
                    (longest, next) =>
                        next.Length > longest.Length ? next : longest,
    // Return the final result as an upper case string.
                    fruit => fruit.ToUpper());

Console.WriteLine(
    "The fruit with the longest name is {0}.",
    longestName);

// This code produces the following output:
//
// The fruit with the longest name is PASSIONFRUIT.

Chunk

Chunk linq

Работает начиная с .NET 6.  Разбивает 1 список на несколько списков с заданым размером.

Пример использования Chunk

// Giving an enumerable
var e = Enumerable.Range(1, 999);

// Here it is. Enjoy :)
var chunks = e.Chunk(29);

// Sample, iterating over chunks
foreach(var chunk in chunks) // for each chunk
{
    foreach(var item in chunk) // for each item in a chunk
    {
        Console.WriteLine(item);
    }
}
Share
Edit

Union

Union linq

Самое простое объяснение того как это работает: представьте, что у вы смержили 2 списка в один, и вызвали Distinct.

Пример использования Union

int[] ints1 = { 5, 3, 9, 7, 5, 9, 3, 7 };
int[] ints2 = { 8, 3, 6, 4, 4, 9, 1, 0 };

IEnumerable<int> union = ints1.Union(ints2);

foreach (int num in union)
{
    Console.Write("{0} ", num);
}

/*
 This code produces the following output:

 5 3 9 7 8 6 4 1 0
*/

Intersect

Intersect linq

Работает аналогично Union, но теперь мы проверяем, какие элементы присутствуют в списке A и списке B. В результирующем наборе будут только элементы, присутствующие в обоих списках. Также здесь: в новом списке только уникальные айтемы. Дубликаты удаляются автоматически.

Пример использования Intersect

int[] id1 = { 44, 26, 92, 30, 71, 38 };
int[] id2 = { 39, 59, 83, 47, 26, 4, 30 };
IEnumerable<int> both = id1.Intersect(id2);

foreach (int id in both)
    Console.WriteLine(id);

/*
 This code produces the following output:

 26
 30
*/

Any

any linq

Any проверяет, удовлетворяет ли хотя бы один элемент вашему условию. Если это так, он возвращает true. Если нет элемента, удовлетворяющего условию, возвращается false.

Пример использования Any

List<int> numbers = new List<int> { 1, 2 };
bool hasElements = numbers.Any();

Console.WriteLine("The list {0} empty.",
    hasElements ? "is not" : "is");

// This code produces the following output:
//
// The list is not empty.

All

all linq

Как следует из названия, проверяет, удовлетворяют ли ВСЕ ваши элементы в списке определенному условию. Если да, то возвращает true, иначе false. 

Пример использования All

class Pet
{
    public string Name { get; set; }
    public int Age { get; set; }
}

public static void AllEx()
{
    // Create an array of Pets.
    Pet[] pets = { new Pet { Name="Barley", Age=10 },
                   new Pet { Name="Boots", Age=4 },
                   new Pet { Name="Whiskers", Age=6 } };

    // Determine whether all pet names
    // in the array start with 'B'.
    bool allStartWithB = pets.All(pet =>
                                      pet.Name.StartsWith("B"));

    Console.WriteLine(
        "{0} pet names start with 'B'.",
        allStartWithB ? "All" : "Not all");
}

// This code produces the following output:
//
//  Not all pet names start with 'B'.

Append

Append linq

Append помещает данный элемент в конец списка.

Пример использования Append

List<int> numbers = new List<int> { 1, 2, 3, 4 };                  
List<int> newNumbers = numbers.Append(5).ToList();
// 1, 2, 3, 4, 5

Prepend

Prepend linq

Помещает заданный элемент в начало списка.

Пример использования Prepend

List<int> numbers = new List<int> { 1, 2, 3, 4 };
List<int> newNumbers = numbers.Prepend(0).ToList();
// 0, 1, 2, 3, 4

MaxBy

MaxBy linq

С помощью MaxBy, а также MinBy мы также можем сделать проекцию на свойство нашего класса и получить объект, где именно это свойство является «самым большим».

Пример использования MaxBy

List<Person> people = new List<Person>
            {
                new Person
                {
                    Name = "John Smith",
                    Age = 20
                },
                new Person
                {
                    Name = "Jane Smith",
                    Age = 30
                }
            };
Console.Write(people.MaxBy(x => x.Age)); //Outputs Person (Jane Smith)

ToLookup

ToLookup linq

Создает универсальный объект Lookup<TKey,TElement> из объекта IEnumerable<T>. Lookup определяется тем, что у нас есть ключ, который может указывать на список объектов (отношение 1 к n). Первый аргумент принимает "key"-селектор. Второй селектор — это «значение». Это может быть сам объект или свойство самого объекта. В конце у нас есть список различных ключей, в которых значения имеют именно этот ключ. Объект LookUp неизменяем. 

Пример использования ToLookup

class Package
{
    public string Company { get; set; }
    public double Weight { get; set; }
    public long TrackingNumber { get; set; }
}

public static void ToLookupEx1()
{
    // Create a list of Packages.
    List<Package> packages =
        new List<Package>
            { new Package { Company = "Coho Vineyard",
                  Weight = 25.2, TrackingNumber = 89453312L },
              new Package { Company = "Lucerne Publishing",
                  Weight = 18.7, TrackingNumber = 89112755L },
              new Package { Company = "Wingtip Toys",
                  Weight = 6.0, TrackingNumber = 299456122L },
              new Package { Company = "Contoso Pharmaceuticals",
                  Weight = 9.3, TrackingNumber = 670053128L },
              new Package { Company = "Wide World Importers",
                  Weight = 33.8, TrackingNumber = 4665518773L } };

    // Create a Lookup to organize the packages.
    // Use the first character of Company as the key value.
    // Select Company appended to TrackingNumber
    // as the element values of the Lookup.
    ILookup<char, string> lookup =
        packages
        .ToLookup(p => p.Company[0],
                  p => p.Company + " " + p.TrackingNumber);

    // Iterate through each IGrouping in the Lookup.
    foreach (IGrouping<char, string> packageGroup in lookup)
    {
        // Print the key value of the IGrouping.
        Console.WriteLine(packageGroup.Key);
        // Iterate through each value in the
        // IGrouping and print its value.
        foreach (string str in packageGroup)
            Console.WriteLine("    {0}", str);
    }
}

/*
 This code produces the following output:

 C
     Coho Vineyard 89453312
     Contoso Pharmaceuticals 670053128
 L
     Lucerne Publishing 89112755
 W
     Wingtip Toys 299456122
     Wide World Importers 4665518773
*/

ToDictionary

ToDictionary linq

ToDictionary работает аналогично ToLookup с одним ключевым отличием. Метод ToDictionary допускает только отношения 1 к 1. Если два элемента используют один и тот же ключ, это приведет к исключению, что ключ уже присутствует.

Пример использования ToDictionary

class Package
{
    public string Company { get; set; }
    public double Weight { get; set; }
    public long TrackingNumber { get; set; }
}

public static void ToDictionaryEx1()
{
    List<Package> packages =
        new List<Package>
            { new Package { Company = "Coho Vineyard", Weight = 25.2, TrackingNumber = 89453312L },
              new Package { Company = "Lucerne Publishing", Weight = 18.7, TrackingNumber = 89112755L },
              new Package { Company = "Wingtip Toys", Weight = 6.0, TrackingNumber = 299456122L },
              new Package { Company = "Adventure Works", Weight = 33.8, TrackingNumber = 4665518773L } };

    // Create a Dictionary of Package objects,
    // using TrackingNumber as the key.
    Dictionary<long, Package> dictionary =
        packages.ToDictionary(p => p.TrackingNumber);

    foreach (KeyValuePair<long, Package> kvp in dictionary)
    {
        Console.WriteLine(
            "Key {0}: {1}, {2} pounds",
            kvp.Key,
            kvp.Value.Company,
            kvp.Value.Weight);
    }
}

/*
 This code produces the following output:

 Key 89453312: Coho Vineyard, 25.2 pounds
 Key 89112755: Lucerne Publishing, 18.7 pounds
 Key 299456122: Wingtip Toys, 6 pounds
 Key 4665518773: Adventure Works, 33.8 pounds
*/

Join

Join linq

Join работает аналогично к SQL left-join.

Пример использования Join

class Person
{
    public string Name { get; set; }
}

class Pet
{
    public string Name { get; set; }
    public Person Owner { get; set; }
}

public static void JoinEx1()
{
    Person magnus = new Person { Name = "Hedlund, Magnus" };
    Person terry = new Person { Name = "Adams, Terry" };
    Person charlotte = new Person { Name = "Weiss, Charlotte" };

    Pet barley = new Pet { Name = "Barley", Owner = terry };
    Pet boots = new Pet { Name = "Boots", Owner = terry };
    Pet whiskers = new Pet { Name = "Whiskers", Owner = charlotte };
    Pet daisy = new Pet { Name = "Daisy", Owner = magnus };

    List<Person> people = new List<Person> { magnus, terry, charlotte };
    List<Pet> pets = new List<Pet> { barley, boots, whiskers, daisy };

    // Create a list of Person-Pet pairs where
    // each element is an anonymous type that contains a
    // Pet's name and the name of the Person that owns the Pet.
    var query =
        people.Join(pets,
                    person => person,
                    pet => pet.Owner,
                    (person, pet) =>
                        new { OwnerName = person.Name, Pet = pet.Name });

    foreach (var obj in query)
    {
        Console.WriteLine(
            "{0} - {1}",
            obj.OwnerName,
            obj.Pet);
    }
}

/*
 This code produces the following output:

 Hedlund, Magnus - Daisy
 Adams, Terry - Barley
 Adams, Terry - Boots
 Weiss, Charlotte - Whiskers
*/

Take

Take linq

Take позволяет нам получить заданное количество элементов из массива. Если у нас меньше элементов в массиве, чем мы хотим получить, то Take() вернет только оставшиеся объекты.

Пример использования Take

int[] grades = { 59, 82, 70, 56, 92, 98, 85 };

IEnumerable<int> topThreeGrades =
    grades.OrderByDescending(grade => grade).Take(3);

Console.WriteLine("The top three grades are:");
foreach (int grade in topThreeGrades)
{
    Console.WriteLine(grade);
}
/*
 This code produces the following output:

 The top three grades are:
 98
 92
 85
*/

Skip

Skip linq

С помощью Skip мы «пропускаем» заданное количество элементов. Если мы пропустим больше элементов, чем содержится в нашем списке, мы получим обратно пустое перечисление. Сочетание Take and Skip может быть очень полезным для таких вещей, как пагинация.

Пример использования Skip

int[] grades = { 59, 82, 70, 56, 92, 98, 85 };

IEnumerable<int> lowerGrades =
    grades.OrderByDescending(g => g).Skip(3);

Console.WriteLine("All grades except the top three are:");
foreach (int grade in lowerGrades)
{
    Console.WriteLine(grade);
}

/*
 This code produces the following output:

 All grades except the top three are:
 82
 70
 59
 56
*/

OfType

OfType linq

OfType проверяет каждый элемент в перечислении на предмет того, относится ли он к заданному типу (унаследованные типы также считаются данным типом) и возвращает их в новом перечислении. Это особенно помогает, если у нас есть нетипизированные массивы (объекты) или нам нужен специальный подкласс данного перечисления.

Пример использования OfType

System.Collections.ArrayList fruits = new System.Collections.ArrayList(4);
fruits.Add("Mango");
fruits.Add("Orange");
fruits.Add("Apple");
fruits.Add(3.0);
fruits.Add("Banana");

// Apply OfType() to the ArrayList.
IEnumerable<string> query1 = fruits.OfType<string>();

Console.WriteLine("Elements of type 'string' are:");
foreach (string fruit in query1)
{
    Console.WriteLine(fruit);
}

// The following query shows that the standard query operators such as
// Where() can be applied to the ArrayList type after calling OfType().
IEnumerable<string> query2 =
    fruits.OfType<string>().Where(fruit => fruit.ToLower().Contains("n"));

Console.WriteLine("\nThe following strings contain 'n':");
foreach (string fruit in query2)
{
    Console.WriteLine(fruit);
}

// This code produces the following output:
//
// Elements of type 'string' are:
// Mango
// Orange
// Apple
// Banana
//
// The following strings contain 'n':
// Mango
// Orange
// Banana

GroupBy

GroupBy linq

Группирует элементы последовательности в соответствии с заданной функцией селектора ключа и создает результирующее значение для каждой группы и ее ключа

Пример использования GroupBy

class Pet
{
    public string Name { get; set; }
    public double Age { get; set; }
}

public static void GroupByEx4()
{
    // Create a list of pets.
    List<Pet> petsList =
        new List<Pet>{ new Pet { Name="Barley", Age=8.3 },
                       new Pet { Name="Boots", Age=4.9 },
                       new Pet { Name="Whiskers", Age=1.5 },
                       new Pet { Name="Daisy", Age=4.3 } };

    // Group Pet.Age values by the Math.Floor of the age.
    // Then project an anonymous type from each group
    // that consists of the key, the count of the group's
    // elements, and the minimum and maximum age in the group.
    var query = petsList.GroupBy(
        pet => Math.Floor(pet.Age),
        pet => pet.Age,
        (baseAge, ages) => new
        {
            Key = baseAge,
            Count = ages.Count(),
            Min = ages.Min(),
            Max = ages.Max()
        });

    // Iterate over each anonymous type.
    foreach (var result in query)
    {
        Console.WriteLine("\nAge group: " + result.Key);
        Console.WriteLine("Number of pets in this age group: " + result.Count);
        Console.WriteLine("Minimum age: " + result.Min);
        Console.WriteLine("Maximum age: " + result.Max);
    }

    /*  This code produces the following output:

        Age group: 8
        Number of pets in this age group: 1
        Minimum age: 8.3
        Maximum age: 8.3

        Age group: 4
        Number of pets in this age group: 2
        Minimum age: 4.3
        Maximum age: 4.9

        Age group: 1
        Number of pets in this age group: 1
        Minimum age: 1.5
        Maximum age: 1.5
    */
}

Reverse

Reverse linq

Reverse переварачивает массив в обратном  порядке

Пример использования Reverse

char[] apple = { 'a', 'p', 'p', 'l', 'e' };
char[] reversed = apple.Reverse().ToArray();
foreach (char chr in reversed)
{
    Console.Write(chr + " ");
}
Console.WriteLine();

/*
 This code produces the following output:

 e l p p a
*/

First

first linq

Возвращает первый элемент который подпадает под выборку.

Пример использования First

int[] numbers = { 9, 34, 65, 92, 87, 435, 3, 54,
                    83, 23, 87, 435, 67, 12, 19 };
int first = numbers.First(number => number > 80);
Console.WriteLine(first);

/*
 This code produces the following output:

 92
*/

Single

Single linq

Single не возвращается сразу после первого вхождения. Отличие от first в том, что Single гарантирует отсутствие второго элемента данного типа/предиката. Поэтому Single должен пройти все перечисление (в худшем случае), если он может найти другой элемент. Если существует более 1 записи, так же если элемент не найден он выдает исключение

Пример использования Single

string[] fruits1 = { "orange" };
string fruit1 = fruits1.Single();
Console.WriteLine(fruit1);

/*
 This code produces the following output:

 orange
*/

FirstOrDefault и SingleOrDefault

 

single or default linq

Если в данном перечислении не найден ни один элемент, он возвращает его по умолчанию (для ссылочных типов null и для типов значений заданное значение по умолчанию). Начиная с .NET6 мы можем передать то, что для нас означает «по умолчанию». Следовательно, мы можем иметь ненулевые ссылочные типы, если захотим.

Пример использования FirstOrDefault и SingleOrDefault

int[] numbers = { };
int first = numbers.FirstOrDefault(); // или SingleOrDefault
Console.WriteLine(first);

/*
 This code produces the following output:

 0
*/

Источник

Comments:

Please log in to be able add comments.