CodeGym /Kurslar /C# SELF /Giriş Span<T>...

Giriş Span<T> və ReadOnlySpan<T> ilə

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

1. Giriş

Massivlər, stringlər və bayt bufferləri ilə işləyərkən tez-tez bu məlumatların bir hissəsinə “baxmaq” lazımdır. Məsələn, substring götürmək, massivdən slice çıxarmaq, giriş streaminin bir hissəsini emal etmək. Köhnə .NET versiyalarında bunun üçün ya məlumatları kopyalamaq (yeni massiv/substring yaratmaq), ya da massivdə lazımi indeksi dövr edərək əl ilə işləmək lazım gəlirdi. Bunların heç biri performans və oxunaqlılıq baxımından ideal deyil.

Köhnə yanaşmaya misal: metodu yalnız böyük massivdən bir hissəni ötürməliyik:

// Köhnə yanaşma — massiv hissəsini kopyalayırıq (səmərəsiz!)
int[] source = new int[] { 1, 2, 3, 4, 5, 6, 7, 8 };
int[] subArray = source.Skip(2).Take(4).ToArray(); // yeni massiv yaradılır

Beləliklə, massivdən (və ya hətta stringin hissəsindən) “slice”i əlavə obyekt yaratmadan effektiv ötürmək lazım gəldikdə, köhnə C# vasitələri zəifdir, xüsusən böyük həcmdə məlumat olduqda.

İşdə burda günün qəhrəmanı — Span<T> yardımınıza gəlir!

2. Span<T> nədir? Əsas ideya

Span<T> eyni tipli T-nin davamlı yaddaş hissəsini təmsil edən tipdir. Onun məqsədi massivlərin, stringlərin, struktur hissələrinin və hətta unmanaged yaddaşın (məsələn, .NET idarə olunan mühitindən kənar ayrılmış yaddaş) parçaları ilə sürətli, təhlükəsiz və effektiv işləməkdir.

Span<T>-in əsas xüsusiyyəti — “yeni massiv yaratmadan slice-lar”. Tutaq ki, bir xətt ölçüsü var və onunla eyni massivdəki istənilən hissəni kopyalamadan ölçə bilərsiniz — indeks xətası riskini minimuma endirərək.

Qısa:

  • Span<T> — yaddaş hissəsinə “pəncərə” və ya “view”, onunla rahat və təhlükəsiz işləmək olur.
  • Yeni yaddaş ayrılmır — resurslara qənaət və GC üçün az iş.
  • Massivlərlə yanaşı string seqmentləri, stackalloc blokları və hətta unmanaged yaddaş ilə işləyir.
  • Adi sinifin sahəsi kimi saxlamaq olmaz: bu stack-only struct tipidir.

Niyə bu önəmlidir?

Yüksək performans tələb edən tapşırıqlarda (fayl parsing, böyük bufferlərin emalı, kriptoqrafiya, serializasiya) bir neçə kopyalamanın qarşısını almaq sürətə böyük təsir göstərə və garbage collector-un yüklənməsini azalda bilər. Həmçinin bu, həmkarlarınıza müasir C# və .NET-dən xəbərdar olduğunuzu göstərər.

3. Span<T>-in əsas istifadəsi: ilk slice

int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

// Massivin bir hissəsinə Span yaratmaq (məsələn, 2-dən 5-ə qədər daxil)
Span<int> middle = new Span<int>(numbers, 2, 4); // indekslər: 2, 3, 4, 5

// Biz alt-massivi görürük: 3, 4, 5, 6
Console.WriteLine(string.Join(", ", middle.ToArray())); // 3, 4, 5, 6

// Span üzərindən edilən dəyişikliklər əsas massivə əks olunur!
middle[1] = 999;
Console.WriteLine(numbers[3]); // 999

Vacib! Span<T> məlumatları kopyalamır, sadəcə massiv hissəsinə işarə edir. Bütün dəyişikliklər həm əsas massivdə, həm də Span-də görünür.

4. Span<T>-in yaradılma üsulları

Massiv əsasında:

int[] arr = { 10, 20, 30, 40, 50 };
Span<int> span = arr; // tam uzunluq
Span<int> slice = arr.AsSpan(1, 3); // 20, 30, 40 elementləri

Massivin bir hissəsi əsasında:

Span<int> part = new Span<int>(arr, 2, 2); // 30, 40 elementləri

stackalloc: stəkdə yaddaş ayırmaq (çox sürətli və heap-ə düşmür):

Span<byte> buffer = stackalloc byte[128];
buffer[0] = 42;

.Slice() metodu ilə:

Span<int> subSpan = span.Slice(1, 2); // 20, 30 elementləri

“Slice”ın vizual sxemi


Əsas massiv:  1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10
                       <--- Span: 3, 4, 5, 6 --->

5. Span<T>-in məhdudiyyətləri və xüsusiyyətləri

  • Stack-only! Adi sinif sahəsi kimi saxlamaq və ya closure-in bir hissəsi etmək olmaz — bu stack tipidir.
  • Sinif sahəsi kimi istifadə etmək və ya async-metodlardan qaytarmaq olmaz (kompilator səhv verəcək).
  • Lambda/anonymous metodlarda capture etmək olmaz — “burada və indi” istifadə edin.
  • Birbaşa serialize etmək və ya threadlər arasında ötürmək olmaz.

Bunun səbəbi odur ki, Span istənilən yaddaş regionuna işarə edə bilər və əgər o heap-ə köçərsə, təhlükəli vəziyyət yaranacaq.

6. İmmutability: ReadOnlySpan<T>

Bəzən yaddaşın bir hissəsinə baxmaq lazımdır, amma onu dəyişdirmək istəmirik. Bu məqsəd üçün immutable variant — ReadOnlySpan<T> mövcuddur.

string text = "Hello, Span!";
ReadOnlySpan<char> letters = text.AsSpan(7, 4); // 'S', 'p', 'a', 'n'
Console.WriteLine(string.Join(", ", letters.ToArray())); // S, p, a, n

// letters[0] = 'Z'; // Səhv: indexator yalnız oxumaq üçündür!

Tipik ssenari — stringin və ya massiv hissəsinin təhlükəsiz ötürülməsi, onu dəyişmək lazım olmadığı yerlərdə.

7. Praktika nümunəsi: massiv və stringləri “slice”ləmək

Tutaq ki, böyük bir string-dən substring çıxarıb içində ədədləri tapıb onların cəmini hesablayan analyzer yazırıq (başlanğıc slice zamanı əlavə kopyalamalar olmadan):

using System;

class Program
{
    static void Main()
    {
        // Tutaq ki, istifadəçi boşluqlarla ayrılmış uzun ədəd stringi daxil edib
        string input = "12 34 56 78 90 123 456 789";
        // Bizə mərkəzdəki ədədlərin cəmi lazımdır, məsələn: 56 78 90

        // Substring çıxarırıq (amma onu kopyalamırıq!)
        ReadOnlySpan<char> center = input.AsSpan(6, 8); // indekslər dinamik hesablana bilər

        // Sayları Split ilə parse edirik (müvəqqəti massiv yaradılır)
        string[] numbers = center.ToString().Split(' ');
        int sum = 0;
        foreach (var str in numbers)
        {
            if (int.TryParse(str, out int num))
                sum += num;
        }
        Console.WriteLine($"Mərkəzi ədədlərin cəmi: {sum}");
    }
}

Müasir parsing kitabxanaları CSV və JSON üçün böyük miqyaslı string məlumatlarla işləyərkən yüksək sürət üçün Span istifadə edirlər — indi onların “sehrinin” nə üzərində qurulduğunu bilirsən.

8. Faydalı nüanslar

Span vs massiv kopyalama

// Köhnə yol: massiv hissəsini kopyalayırıq
int[] arr = Enumerable.Range(0, 1000000).ToArray();
int[] firstThousand = arr.Take(1000).ToArray(); // 1000 elementlik yeni massiv yaradıldı

// Yeni yol: Span
Span<int> bestThousand = arr.AsSpan(0, 1000); // heç nə kopylanmır!
bestThousand[0] = 42; // arr-də də dəyişir

Fərq xüsusilə fayl parsingində, şəbəkə bufferlərinin emalında və ikili məlumatlarla işləməkdə nəzərə çarpır.

Real tapşırıqlarda tətbiq: niyə Span-ı bilmək lazımdır

  • Yüksək performanslı parsing və string/binary məlumatların emalı. Müasir serializasiya kitabxanaları (məsələn, System.Text.Json, Span Microsoft sənədlərində) performansı artırmaq üçün Span-dən istifadə edir.
  • Bufferizasiya və fayl oxuma — böyük bufferləri kopyalamadan kəsib istifadə etmək.
  • Memory məhdud olan cihazlarda (embedded, IoT) işləmək — Memory/Spans üzrə rəsmi sənəd.
  • Şəkil və audio alqoritmləri, burada sürət və əlavə allokasiyaların olmaması vacibdir.
  • CSV, JSON, XML parsing-in sürətləndirilməsi Span ilə — xüsusən .NET 8/9-da.

Müsahibələrdə Span haqqında suallar .NET Core 2.1+ çıxdıqdan sonra ortaya çıxıb və .NET 9-da daha çox arzu olunan bilik hesab olunur.

Vizual sxem: Span harada, massiv harada


+--------------------+
|   int[]  massiv    |
|  1 2 3 4 5 6 7 8   |
+--------------------+
       ^       ^
       |       |
   [ 2, 3, 4, 5 ]  <-- Span<int> "yaddaş pəncərəsi" (slice)

Span<T> ayrıca massiv deyil, məlumatın bir hissəsinə “şəffaf linza”dır.

Digər kolleksiyalardan fərqi: müqayisə cədvəli

Tip Məlumat saxlayır? Elementləri dəyişmək olar? Ölçünü dəyişmək olar? Slice zamanı kopyalayır? Harada yaşayır?
int[]
Bəli Bəli Xeyr Bəli (.Take vasitəsilə) Heap
List<int>
Bəli Bəli Bəli Bəli Heap
Span<int>
Xeyr Bəli Xeyr Stack
ReadOnlySpan<int>
Xeyr Xeyr Xeyr Xeyr Stack

9. Span/ReadOnlySpan ilə işləyərkən tipik səhvlər

Səhv №1: Span-ı sinif sahəsi kimi saxlamağa cəhd etmək. Kompilator “Span type may not be used in this context” xəta verəcək. Bu intentional dizayndır: Span-ı sahədə saxlamaq təhlükəlidir.

Səhv №2: Span-ı async-metoddan qaytarmaq. Belə etmək olmaz, çünki async-metodlar heap-ə köçə bilər. Bunun əvəzinə massiv və ya başqa tip istifadə edin.

Səhv №3: İnsanlar unuturlar ki, Span vasitəsilə edilən dəyişikliklər əsas massivə əks olunur. Bu xarici dəyişikliklərə səbəb ola və qeyri-müəyyən davranış yarada bilər.

2
Tapşırıq
C# SELF, səviyyə, dərs
Bağlanıb
`Span` ilə modifikasiya
`Span` ilə modifikasiya
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION