CodeGym /Kurslar /C# SELF /Memory<T> və ReadOnlyMemory<T>

Memory<T> və ReadOnlyMemory<T>

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

1. Giriş

Hər şey .NET tərtibatçıları böyük həcmli məlumatlarla işləməyi sürətləndirmək və bunu rahat və təhlükəsiz etmək istəməklə başladı. Əvvəlcə Span<T> ortaya çıxdı — bu ardıcıl yaddaş sahəsinə "pəncərə"dir, massivdən, sətrdən və hətta .NET-dən kənarda ayrılmış yaddaşdan (məsələn P/Invoke vasitəsilə) hissəni göstərə bilər.

Amma Span<T>-in güclü bir məhdudiyyəti var: o həmişə yalnız stack-də yaşamalıdır. Onu sinif sahələrində saxlamaq olmaz, metodlardan qaytarmaq olmaz, asinxron metodlar arasında ötürmək olmaz. Səbəb — təhlükəsizlik: kimsə artıq mövcud olmayan yaddaşa istinad saxlayarsa, bu tətbiqin çökməsi ilə nəticələnə bilər.

Bəzən metodlardan yaddaş dilimlərini qaytarmaq, kolleksiyalarda və ya sinif sahələrində saxlamaq və asinxron API-lərdə istifadə etmək lazım gəlir. İşdə burada Memory<T> səhnəyə çıxır — əslində bu Span<T>-in təhlükəsiz "uzunömürlü" versiyasıdır, o heap-də yaşaya, thread-lər arasında ötürülə, property və obyektlərdə saxlanıla və normal .NET obyekti kimi davrana bilər.

Daha bir "ağsaqqal" var — ReadOnlyMemory<T>, adından da göründüyü kimi, onun altında yatan məlumatları dəyişməyə imkan vermir, amma lazım olan yerdə oxumağa icazə verir.

2. Span<T> və Memory<T> arasındakı fərq

Göstəriş üçün kiçik bir cədvəl:

Span<T>
Memory<T>
Harada yaşayır Yalnız stack-də (stack only) Stack-də və heap-də (heap/stack)
Sinif sahəsində saxlamaq olar ❌ Ola bilməz ✅ Ola bilər
Metoddən qaytarmaq olar ❌ Olmaz ✅ Ola bilər
Asinxron/await-metodlarda ❌ Olmaz ✅ Ola bilər
Dəyişdirilə bilən ✅ Həmçinin ReadOnlySpan<T> var ✅ Həmçinin ReadOnlyMemory<T> var
Məlumatı slice etmək mümkündür ✅ Bəli ✅ Bəli

Əgər metod daxilində məlumatlar üzərində sürətlə "gəzintiyə" çıxmaq lazımdırsa — Span<T>-dən istifadə edin. Əgər nəticəni xaricə qaytarmaq və ya sinif sahəsində saxlamaq lazımdırsa — Memory<T>-dən istifadə edin. Əgər məlumatlar yalnız oxunacaqsa — ReadOnlyMemory<T>-i götürün.

3. Memory<T>-in signaturası və əsas quruluşu

Hər şey adi qaydada: Memory<T> generikdir, yəni universal tipdir. Məsələn, Memory<int>, Memory<byte>, Memory<char> və ya hətta Memory<MyType> yarada bilərsiniz. Memory<T>-in içində massivə, sətə və ya digər mənbəyə göstərici və həmçinin aralığı (hansı indeksdən və neçə element) saxlayan məlumat var.

İşləmək üçün Memory<T>-dən sürətli giriş almaq istəsəniz, onun Span xassəsindən istifadə edirlər — bu zaman siz dərhal Span<T> əldə edirsiniz və onu sinxron metod daxilində istifadə edə bilərsiniz.

4. Memory<T>-in yaradılması: Praktika

Nümunə 1. Massivdən yaratmaq

int[] numbers = { 1, 2, 3, 4, 5, 6 };
Memory<int> memory = new Memory<int>(numbers); // Bütün massiv

// "slice" — massivdən bir hissə götürmək olar
Memory<int> slice = memory.Slice(2, 3); // elementlər 2, 3 və 4

Nümunə 2. Sətrdən yaratmaq ( Memory<char> vasitəsilə )

string text = "Salam, dünya!";
Memory<char> charMemory = text.AsMemory(); // Bütün mətn yaddaş kimi
Memory<char> subMemory = charMemory.Slice(7, 3); // 7-ci simvoldan 3 simvol ("dünya"nın hissəsi)

Nümunə 3. ReadOnlyMemory<T>-in istifadəsi

Tam eynidir, sadəcə dəyişikliklərə icazə verilmir:

int[] data = { 10, 20, 30, 40 };
ReadOnlyMemory<int> readOnly = data; // Bu obyekt vasitəsilə dəyişdirməyə icazə verməyəcək

5. Memory<T> və Span<T> arasında çevirmə

Memory<T>-lə Span<T>-in kimi çevik və sürətli işləməsi birbaşa mümkün deyil — onlar fərqli məqsədlər üçün nəzərdə tutulub. Amma əslində yaddaşın bir parçasını sürətlə işləmək lazım gəlsə, memory-dən dərhal Span<T> əldə edə bilərsiniz, onun Span xassəsi vasitəsilə:

void ProcessData(Memory<int> memory)
{
    Span<int> span = memory.Span;
    for (int i = 0; i < span.Length; i++)
    {
        span[i] += 100;
    }
}

Diqqət edin: Span<T> yalnız metod daxilində işləyir. Əgər onu xaricə qaytarmağa cəhd etsəniz, kompilyator səhv verəcək.

ReadOnlyMemory<T> üçün də vəziyyət oxşardır, amma siz ReadOnlySpan<T> alacaqsınız və bu vasitə ilə məlumatları dəyişdirmək mümkün olmayacaq:

void PrintData(ReadOnlyMemory<int> memory)
{
    ReadOnlySpan<int> roSpan = memory.Span;
    foreach (var item in roSpan)
        Console.WriteLine(item);
}

6. Real tapşırıqlarda istifadə

Asinxron məlumat emalı

Era burada Memory<T>-in həqiqi vaxtıdır. O, asinxron metodlar üçün uyğundur! Məsələn, faylın asinxron oxunması:

using System.IO;
using System.Threading.Tasks;

public async Task ReadFileAsync(string path)
{
    byte[] buffer = new byte[4096];
    using var stream = File.OpenRead(path);
    int bytesRead = await stream.ReadAsync(buffer.AsMemory(0, buffer.Length));
    // İndi buffer ilə işləyə bilərsiniz
}

Burada AsMemory bu tamponu birbaşa asinxron metoda ötürür və Span<T>-də olduğu kimi scope və yaddaş problemi yaranmır.

Yaddaş dilimlərini property və sahələrdə saxlamaq

Bəzən böyük massivdən bir "hissə"ni sonradan ötürmək üçün saxlayan sinif yaratmaq lazımdır:

class DataChunk
{
    public Memory<byte> Data { get; }

    public DataChunk(Memory<byte> data)
    {
        Data = data;
    }
}

7. Kolleksiyalar, sətirlər və massivlərlə istifadə

Massivlərlə

Çox vaxt belə olur:

byte[] bytes = { 1, 2, 3, 4, 5 };
Memory<byte> mem = bytes;          // Bütün massiv
Memory<byte> part = mem.Slice(2);  // Üçüncü elementdən sona kimi

Sətirlərlə

AsMemory() vasitəsilə:

string hello = "Hello, Memory!";
ReadOnlyMemory<char> mem = hello.AsMemory(6, 6); // "Memory"

Kolleksiyalarla (məsələn, List<T>)

Birbaşa List<T>-dən Memory<T> yaratmaq mümkün deyil. Yalnız massiv vasitəsilə:

List<int> list = new List<int> { 1, 2, 3 };
Memory<int> mem = list.ToArray(); // Kopyadır, istinad deyil!

Diqqətli olun: kopyalanmadan qaçmaq istəyirsinizsə — məlumatları massivdə saxlayın.

8. Memory<T> ilə işləyərkən tipik səhvlər

Səhv №1: sinif sahəsində Span<T>-dən istifadə etməyə cəhd. Span<T>-i sinif sahəsində saxlamaq olmaz, çünki o stack-ə bağlıdır. Kompilyator səhv verəcək. Heap-də saxlamaq üçün Memory<T>-dən istifadə edin.

Səhv №2: slice etdikdə məlumatın kopyalanacağını düşünmək. Memory<T> məlumatları kopyalamır, o mövcud massivdə "pəncərə" yaradır. Əgər bir Memory<T> vasitəsilə məlumatı dəyişsəniz, eyni yaddaşa istinad edən digər obyektlərdə də bu görünəcək.

Səhv №3: birbaşa List<T>-dən Memory<T> yaratmağa çalışmaq. Memory<T> yalnız massivlərlə çalışır, çünki List<T> yaddaşdakı yerləşimini dəyişə bilər. Siyahını massivə çevirmək üçün ToArray()-dan istifadə edin.

Səhv №4: dəyişməz məlumatlar üçün ReadOnlyMemory<T>-i görməməzlikdən gəlmək. Əgər məlumatları dəyişdirmək lazım deyilsə, daha təhlükəsiz olduğu üçün ReadOnlyMemory<T>-dən istifadə edin, Memory<T>-dən yox.

2
Tapşırıq
C# SELF, səviyyə, dərs
Bağlanıb
Memory ilə Asinxron İş
Memory ilə Asinxron İş
1
Sorğu/viktorina
, səviyyə, dərs
Əlçatan deyil
C#-da yaddaş
.NET-də yaddaşın quruluşu
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION