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:
|
|
|
|---|---|---|
| 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.
GO TO FULL VERSION