CodeGym /Kurslar /C# SELF /Kolleksiyaların düzləşdirilməsi

Kolleksiyaların düzləşdirilməsi SelectMany ilə

C# SELF
Səviyyə, Dərs
Mövcuddur

1. Giriş

Əgər sən sadəcə "düz" obyekt listi ilə işləyirsənsə (məsələn, əvvəlki dərslərdəki məhsullar və ya istifadəçilər listi), hər şey asandır: filterləyirik, transformasiya edirik, sıralayırıq. Amma real biznes tətbiqlərində, müsahibələrdə (və hətta ev tapşırıqlarında!) tez-tez içində başqa kolleksiyalar olan kolleksiyalar rast gəlinir.

Məsələn, User klası var, onun sifarişlər listi var. Və ya Product klası var, onun rəylər listi var. İndi tapşırıq: bütün istifadəçilərin bütün sifarişlərinin siyahısını necə alaq? Və ya bütün məhsulların bütün rəylərini toplamaq? Bəs bütün sifarişlərdəki bütün məhsulların siyahısını necə çıxaraq?

Belə hallarda adi Select və hətta C#-ın sehrli gücü bəzən çatmır. Burada səhnəyə qəhrəman çıxır — SelectMany operatoru, "listlərin listi"ni sadəcə "list"ə çevirir.

Mətbəx analoqu

Təsəvvür elə, səndə bir qutu var, içində başqa qutular var, onların içində isə peçenyelər. Tapşırıq — bütün peçenyeləri bir böyük qaba tökməkdir. Sən hər qutudan bir-bir peçenye götürməzsən, birbaşa bütün qutuları və peçenyeləri qaba tökərsən.

SelectMany də məhz bunu edir: o, kolleksiyanı "düzləşdirir", "qutularla qutuları" bir "qab"a çevirir.

2. Tətbiq nümunəsini xatırlayaq

Keçən dərslərdə istifadəçilər və məhsullarla işləmək üçün sadə bir tətbiq yaratmışdıq. Bu dərs üçün "iç-içə" elementlər kolleksiyası əlavə edirik.

Tutaq ki, belə klazlarımız var:


public class User
{
    public string Name { get; set; }
    public List<Order> Orders { get; set; }
}

public class Order
{
    public int Id { get; set; }
    public List<Product> Products { get; set; }
}

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

İndi bizdə istifadəçilər listi var, hər birinin sifarişlər listi var, hər sifarişin isə məhsullar listi var.

Bütün istifadəçilərin bütün sifarişlərindəki bütün məhsulları necə almaq olar?

Tutaq ki, bizə bütün məhsulların ümumi siyahısı lazımdır — iç-içə olmadan, sadəcə bir "qab"da. Əgər adi Select istifadə etsək, nə baş verəcək?


List<User> users = ...; // güman edək ki, hardasa data var

var allOrders = users.Select(u => u.Orders);

allOrders dəyişənin tipi olacaq... IEnumerable<List<Order>>. Yəni "listlərin listi", yəni sifariş kolleksiyaları çoxluğu. Biz isə bir düz sifarişlər listi istəyirik.

Bir də belə yoxlayaq:


var allProducts = users.Select(u => u.Orders)
                       .Select(oList => oList.Select(o => o.Products));

İndi nə oldu? İndi bizdə "listlərin listlərinin listi" var! Yəni strukturu sadələşdirmək əvəzinə, daha da çətinləşdiririk.

3. Həll: SelectMany operatoru

Bax, burada SelectMany-nin bütün gücü ortaya çıxır. Onun işi — iç-içə kolleksiyaları bir düz kolleksiyaya "flatten" etməkdir.

Ümumi sintaksis:


collection.SelectMany(item => item.DaxiliKolleksiya)

Bizim halda:


var allOrders = users.SelectMany(u => u.Orders);
// Tip: IEnumerable<Order>

İndi bizdə sadəcə bütün istifadəçilərin bütün sifarişlərinin ardıcıllığı var.

Daha da dərin gedək! Bütün istifadəçilərin bütün sifarişlərindəki bütün məhsullar:


var allProducts = users
    .SelectMany(u => u.Orders)
    .SelectMany(o => o.Products);
// Tip: IEnumerable<Product>

Bəs bir sətrdə?

Bəli, belə də olar:


var allProducts = users.SelectMany(u => u.Orders.SelectMany(o => o.Products));

Bu zənciri hissə-hissə oxumaq daha rahatdır, xüsusən də robot deyilsənsə.

4. Böyük nümunədə izah edirik

Gəlin test dataları yaradaq və SelectMany-nin necə işlədiyinə baxaq.


var users = new List<User>
{
    new User
    {
        Name = "Alisa",
        Orders = new List<Order>
        {
            new Order
            {
                Id = 1,
                Products = new List<Product>
                {
                    new Product { Name = "Peçenye" },
                    new Product { Name = "Süd" },
                }
            },
            new Order
            {
                Id = 2,
                Products = new List<Product>
                {
                    new Product { Name = "Şokolad" }
                }
            }
        }
    },
    new User
    {
        Name = "Bob",
        Orders = new List<Order>
        {
            new Order
            {
                Id = 3,
                Products = new List<Product>
                {
                    new Product { Name = "Kofe" },
                    new Product { Name = "Çay" }
                }
            }
        }
    }
};

Bütün məhsulları bir qaba "tökürük"


var allProducts = users
    .SelectMany(u => u.Orders)
    .SelectMany(o => o.Products);

foreach (var product in allProducts)
{
    Console.WriteLine(product.Name);
}

Proqramın nəticəsi:

Peçenye
Süd
Şokolad
Kofe
Çay

Hər mərhələdə nə baş verir?

  1. users.SelectMany(u => u.Orders) — bütün istifadəçiləri götürürük və onların sifarişlərini bir kolleksiyaya "düzləşdiririk" (indi bizdə 3 sifariş var).
  2. .SelectMany(o => o.Products) — bütün sifarişlərdəki məhsulların siyahısını düzləşdiririk.

Diqqət: Əgər adi Select istifadə etsəydik, kolleksiyaların kolleksiyasını alardıq (məsələn, IEnumerable<List<Product>>). Amma SelectMany ilə biz "düz" məhsullar kolleksiyası alırıq: IEnumerable<Product>.

5. Sxemdə necə görünür

SelectMany-dən əvvəl


users (List<User>)
   └── User #1
   │      └── Orders (List<Order>)
   │           ├── Order #1 -> Products (List<Product>)
   │           └── Order #2 -> Products (List<Product>)
   └── User #2
          └── Orders (List<Order>)
               └── Order #3 -> Products (List<Product>)

Birinci SelectMany(u => u.Orders)-dən sonra


IEnumerable<Order>: [Order #1, Order #2, Order #3]

İkinci SelectMany(o => o.Products)-dən sonra


IEnumerable<Product>: [Peçenye, Süd, Şokolad, Kofe, Çay]

6. Müqayisə: Select və SelectMany

Select SelectMany
Nə qaytarır "Kolleksiyaların kolleksiyası" Düz kolleksiya
Nümunə
users.Select(u => u.Orders)
→
IEnumerable<List<Order>>
users.SelectMany(u => u.Orders)
→
IEnumerable<Order>
İstifadə olunur Əgər iç-içə strukturu saxlamaq lazımdırsa Əgər bir düz ardıcıllıq lazımdırsa

Cədvəl: hansı operatoru nə vaxt istifadə etməli

Nə etmək lazımdır Nə istifadə etməli Nəticə
Listlərin listini almaq (düzləşdirməmək) Select
IEnumerable<ICollection<T>>
Bir "düz" ardıcıllıq almaq SelectMany
IEnumerable<T>
"Valideyn-uşaq" cütlərini almaq SelectMany nəticə selektoru ilə
IEnumerable<CustomType>
Sadəcə "düz" kolleksiyanın elementlərini transformasiya etmək Select
IEnumerable<TransformedType>

7. Faydalı nüanslar

Query Syntax sintaksisi ("SQL görünüşü" sevənlər üçün)

Xüsusilə ifadəliliyi sevənlər üçün — LINQ sorğuları SQL-ə bənzər sintaksisdə də yazmaq olar:


var allProducts = from user in users
                  from order in user.Orders
                  from product in order.Products
                  select product;

Bu kod SelectMany zəncirinin etdiyini edir, sadəcə çoxsəviyyəli "from" kimi görünür.

Matrisin "düzləşdirilməsi"

SelectMany-nin klassik tətbiqi — ikiölçülü massiv və ya listlərin listi ilə işləməkdir.


List<List<int>> matrix = new List<List<int>>
{
    new List<int> { 1, 2, 3 },
    new List<int> { 4, 5 },
    new List<int> { 6 }
};

Bütün ədədlərin bir listini necə almaq olar?


var flat = matrix.SelectMany(row => row);
foreach (var value in flat)
{
    Console.Write(value + " "); // 1 2 3 4 5 6
}

8. İrəli sintaksis

Bəzən SelectMany-nin overloadunu istifadə edəndə, yalnız daxili kolleksiya elementini yox, həm də xarici məlumatı qaytara bilərsən.

Nümunə: hər sifarişdəki hər məhsul üçün "İstifadəçi adı — Məhsul adı" cütlərini almaq lazımdır.


var userProductPairs = users.SelectMany(
    user => user.Orders.SelectMany(
        order => order.Products,
        (order, product) => new { UserName = user.Name, ProductName = product.Name }
    )
);

foreach (var pair in userProductPairs)
{
    Console.WriteLine($"{pair.UserName} sifariş etdi {pair.ProductName}");
}

Burada lambda-nın ikinci parametri — xarici kolleksiya elementidir. Çox faydalıdır, çünki xarici səviyyələrdəki məlumatı "itirməmək" olur.

9. Praktik ssenarilər

Çoxlu sifariş, çoxlu məhsul

Sən — onlayn mağaza proqramçısısan. Bütün müştərilərin neçə unikal məhsul aldığını saymaq istəyirsən? Bax belə:


var uniqueProductNames = users
    .SelectMany(user => user.Orders)
    .SelectMany(order => order.Products)
    .Select(product => product.Name)
    .Distinct();

foreach (var name in uniqueProductNames)
{
    Console.WriteLine(name);
}

Iyerarxiyadan düz kolleksiya formalaşdırmaq

Səndə şirkət şöbələrinin listi var, hər şöbədə işçilər var. Bütün şirkət üzrə bütün işçilərin siyahısını almaq istəyirsən:


var allEmployees = departments.SelectMany(d => d.Employees);

Sətirlərlə işləmək: sözlər massivini simvollara "düzləşdirmək"


string[] words = { "hello", "world" };
var allChars = words.SelectMany(w => w.ToCharArray());
foreach (var c in allChars)
{
    Console.Write(c + " "); // h e l l o w o r l d
}

10. Tipik səhvlər və nüanslar

İndi sən SelectMany-nin gücündən ilhamlanmısan, vaxtdır bir neçə tez-tez rast gəlinən səhvlər barədə xəbərdarlıq edək.

Ən məşhur — Select-in "düz" kolleksiya qaytaracağını gözləmək. Əslində, əgər daxili funksiya kolleksiya qaytarırsa, o həmişə kolleksiyaların kolleksiyasını qaytarır. Nəticədə ikiqat dövrələr yazmalı olursan və ya tiplərdə çaşırsan.

İkinci səhv — "düzləşdirmə" zamanı xarici element barədə məlumatı itirmək: məsələn, sadəcə məhsullarla işləyirsənsə, hansı sifarişdə və ya hansı istifadəçidə olduğunu bilməyəcəksən, əgər nəticə selektoru olan overload istifadə etməsən.

Üçüncü nüans — əgər daxili listlər null ola bilərsə, SelectMany istifadə etməzdən əvvəl qoruma əlavə et: ya filterlə, ya da boş listlə əvəzlə, yoxsa NullReferenceException alacaqsan, hələ F5-ə basmamış.

2
Tapşırıq
C# SELF, səviyyə, dərs
Bağlanıb
Daxili siyahılardan bütün əşyaların çıxarılması
Daxili siyahılardan bütün əşyaların çıxarılması
1
Sorğu/viktorina
, səviyyə, dərs
Əlçatan deyil
LINQ-ə giriş
LINQ əsasları: sadə sorğular və dataların filtrlenməsi
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION