CodeGym /Kurslar /C# SELF /Axınların bağlanması və resursların azad olunması (

Axınların bağlanması və resursların azad olunması ( using)

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

1. C#-da resursları düzgün necə azad etmək lazımdır?

Gəlin təsəvvür edək ki, ƏS (əməliyyat sistemi) ciddi bir kitabxanaçı kimidir. Sən bir kitab götürürsən (fayl/axın açırsan), oxuyursan, sonra isə... qaytarmağı unudursan! Kitabxanaçı əsəbləşir: "Necə yəni — kitab hələ də səndədir?!" Axınlarla da eyni vəziyyətdir. Açıq axın resursları tutur: fayl deskriptoru, bir parça yaddaş, üstəlik faylı başqa proqramlar üçün bloklayır.

Əgər axını bağlamasan, nəticə "nəsə işləmir"dən tutmuş "hər şey dağıldı, heç kim bu fayla yaza bilmir"ə qədər ola bilər. Proqramda çoxlu açıq axınlar varsa — sistem resurs sızması yaşaya və işləməyi dayandıra bilər.

Təhlükə nədədir?

  • Fayl bağlanmır, komandalar diskinə çatmır (məsələn, yazarkən — məlumatlar buffer-də qala bilər).
  • Fayl başqa proseslər üçün bloklanır — kolleqalar və başqa proqramlar əsəbləşir.
  • Deskriptor limiti: Windows/Linux-da proseslərin açıq fayl/axın limiti var.

IDisposable interfeysi

İdarə olunmayan resurslarla işləyən istənilən class (axınlar, fayllar, verilənlər bazası, socketlər) IDisposable interfeysini implement etməlidir.


public interface IDisposable
{
    void Dispose();
}

Dispose() metodunun içində adətən bütün bədbəxt resurslar azad olunur: fayl nəhayət bağlanır, bağlantılar qırılır, yaddaş boşalır.

Axınlar — əlində müxtəlif vacib resurslar tutan obyektlərdir: fayllar, bağlantılar, yaddaş. Əgər onları bağlamasan, fayl bloklanmış qala bilər (sənin Word deyəcək "fayl başqa proqram tərəfindən açılıb!"), sistem isə yaddaşsız qala bilər. Ona görə də axınla iş bitəndə onu azad etmək çox vacibdir.

.NET-də axınlar IDisposable interfeysini implement edir. Bu o deməkdir ki: onları bağlamaq lazımdır, Dispose() çağırmaqla (və ya sadəcə using blokuna salmaqla).

2. Axını bağlama variantları: təhlükəlidən etibarlıya

Variant 1. "Əl ilə" bağlama: belə etmə!

Bu köhnə üsuldur. Mən əvvəlki nümunələrdə göstərmiş olsam da, müasir proqramlaşdırmada bunu demək olar ki, heç kim istifadə etmir :P


var stream = new FileStream("file.txt", FileMode.Open);
// Axınla işləyirik
stream.Close(); // və ya stream.Dispose()

Problem: əgər açmaqla bağlama arasında səhv/exception baş versə, fayl açıq və bloklanmış qalacaq. Bu elə bil kitabxanadan kitabı götürüb pizza qoxusu hiss edib qaçırsan...

Variant 2. try...finally istifadə edirik


FileStream stream = null;
try
{
    stream = new FileStream("file.txt", FileMode.Open);
    // Axınla işləyirik
}
finally
{
    if (stream != null)
        stream.Dispose();
}

Bu variant etibarlıdır: finally hətta səhv olsa belə mütləq işləyəcək. Amma etiraf edək, belə yazmağa tənbəllik gəlir.

Variant 3. Gözəl və təhlükəsiz: using operatoru

Klassik sintaksis (using ( ... ) { ... })


using (var stream = new FileStream("file.txt", FileMode.Open))
{
    // Axınla işləyirik
}
// Burada stream.Dispose() avtomatik çağırılacaq!

Əsas fikir — using blokunun içində hər şey axınla işləyir, blok bitəndə isə fayl bağlanır, hətta nəsə səhv getsə belə (məsələn, exception atılsa).

Variant 4. Müasir sintaksis

Müasir sintaksis (using var)


using var stream = new FileStream("file.txt", FileMode.Open);
// Axınla işləyirik
// ... Dispose avtomatik çağırılacaq, dəyişən scope-dan çıxanda

Əladır! Artıq əlavə mötərizə və bloklar yazmağa ehtiyac yoxdur.

Bu "qapağın altında" necə işləyir?

using operatorunu kompilyator sənin əvəzinə həmin try...finally-ə çevirir. Zarafat: "using sənin əvəzinə təmiz kod yazır — bəlkə tezliklə qəhvə içib Stack Overflow-da vaxt keçirəcək?".

Klassik və müasir using arasındakı fərq

Klassik using-bloku using var (declaration)
Görünüş
using (var x = ...) { ... }
using var x = ...; ...
Scope Blokun mötərizələri içində Cari blokun sonuna qədər (metod, dövr və s.)
Qısalığı Bir az daha çox söz Qısa, az indent
Başlayır C# 1.0 C# 8.0 və yuxarı

3. using-elanları nədir?

5 il əvvəl dünya IDisposable-obyektlərlə işləmək üçün yeni, qısa bir yol gördü.

using-elanı — blok əvəzinə dəyişəni using açarı ilə elan edirsən və o, cari blokun sonunda (məsələn, metod və ya dövr) avtomatik azad olunacaq, əlavə blokun mötərizələrinin sonunda yox.


using var stream = new FileStream("file.txt", FileMode.Open);
// Axınla işləyirik
Console.WriteLine(stream.Length);

// Burada fayl hələ də açıqdır!

// ... metodun sonu
// stream.Dispose() burada avtomatik çağırılacaq

Klassik using-dən əsas fərqlər:

  • Mötərizə lazım deyil, əlavə kod bloku yaranmır.
  • Dəyişən bütün blokun sonuna qədər əlçatandır (adətən — metod, bəzən — dövr, class, əgər class səviyyəsində elan olunubsa).
  • Resursun azad olunması yalnız blok bitəndə baş verəcək.

Niyə bu cool-dur?

  • Daha az nested səviyyə — kod xeyli qısa və oxunaqlı oldu.
  • Bir neçə resursla işləmək asanlaşdı — bir neçə using-dəyişəni ard-arda elan et, metod bitəndə hamısı azad olunacaq.
  • Səhv etmək şansı azaldıDispose() çağırmalı olduğun scope-u qaçırmayacaqsan.

4. Müqayisə edirik: klassik vs. müasir using

Gəlin kod şəklində müqayisəyə baxaq.

Klassik üsul


using (var reader = new StreamReader("input.txt"))
{
    using (var writer = new StreamWriter("output.txt"))
    {
        string line;
        while ((line = reader.ReadLine()) != null)
        {
            writer.WriteLine(line.ToUpper());
        }
    }
} // Burada hər iki fayl bağlanacaq

Müasir üsul (C# 8+)


using var reader = new StreamReader("input.txt");
using var writer = new StreamWriter("output.txt");

string line;
while ((line = reader.ReadLine()) != null)
{
    writer.WriteLine(line.ToUpper());
}
// Hər iki fayl burada, metoddan çıxanda bağlanacaq

Daha sadə görünür, hə? Xüsusilə daha çox nested olsa — müasir üsul həyatı xeyli asanlaşdırır.

5. Dispose() nə vaxt və harada işləyir?

Burada tələbələr tez-tez səhv edir: düşünürlər ki, Dispose istifadə olunduğu sətrin dərhal ardınca çağırılır — amma belə deyil!

Bu nümunəyə bax:


void MyMethod()
{
    using var fileStream = new FileStream("data.bin", FileMode.Open);
    // ... çoxlu kod, bəlkə də dövrlər və nested çağırışlar
    // fileStream hələ də açıqdır!

    // Burada fileStream-ə çıxış edə bilərik
}
// Burada, metodun }-də, fileStream.Dispose() çağırılır

Vacib məqam: Əgər using-dəyişəni dövrün içində elan etsən, Dispose hər iterasiyadan sonra çağırılacaq.


foreach (var path in filePaths)
{
    using var reader = new StreamReader(path);
    // reader ilə işləyirik
} // reader.Dispose() hər iterasiyadan sonra çağırılacaq (faylı bağlayacaq)

6. Köhnə kodu köçürəndə səhvlər

Bəzən olur ki, köhnə kodu köçürürsən və ya klassik using nümunəsini kopyalayırsan, amma dəyişənin "ömür"ü mötərizə scope-dan daha uzun olmalıdır. Onda klassik variant sənə uyğun deyil, amma using-elanları — ideal.

Amma nüanslar var. Məsələn, dövrün içində iki resurs varsa, amma biri digərindən uzun "yaşamalıdır" — onları düzgün ardıcıllıqla elan et:


using var resource1 = ...;
for (int i = 0; i < 10; i++)
{
    using var resource2 = ...;
    // resource2 bir iterasiya yaşayır
    // resource1 — bütün funksiya
}

7. Praktika

Tədris tətbiqimizi inkişaf etdirməyə davam edirik — kiçik bir qəhvə sifarişi simulyatoru, hansı ki, sən keçən dərslərdə təkmilləşdirmişdin. İndi qoy o, sifariş tarixçəsini tekst faylına yaza və startda oxuya bilsin.

Addım 1: Sifarişi fayla yazırıq


using var writer = new StreamWriter("orders.txt", append: true);
writer.WriteLine("Qəhvə: Latte; Süd: Yulaf; Ölçü: Böyük");

// StreamWriter konstruktorunun ikinci parametri append: true göstərir ki, faylı yenidən yazmaq yox, əlavə etmək istəyirik.

Addım 2: Sifariş tarixçəsini oxuyuruq


using var reader = new StreamReader("orders.txt");
string? line;
while ((line = reader.ReadLine()) != null)
{
    Console.WriteLine($"Sifariş: {line}");
}

Və proqram bu metodu bitirən kimi fayllar avtomatik bağlanacaq.

8. using-elanları ilə işləmək üçün ən yaxşı praktika

1. IDisposable implement edən obyektlər üçün həmişə using istifadə et
.NET-də fayllar, axınlar, resurslarla işləyən class-ların çoxu bu interfeysi implement edir. Bu bir siqnaldır: məni using ilə azad et!

2. Scope-u unutma: using-var dəyişəni lazım olmayan yerdə elan etmə
Əgər dəyişən cəmi bir neçə sətrə lazımdırsa — onu orada istifadə et, tez elan etmə.

3. Azad etmə ardıcıllığını unutma
Bir neçə using-dəyişəni ard-arda elan etsən — Dispose tərsinə çağırılacaq:


using var first = new Resource("Birinci");
using var second = new Resource("İkinci");
// ... iş
// Əvvəlcə second üçün Dispose, sonra first üçün

Bəzən bu vacibdir, əgər bir resurs digərindən asılıdırsa (məsələn, yazı axını fayldan əvvəl azad olunmalıdır).

4. using-elanını metoddan kənarda istifadə etmə
using-elanları class səviyyəsində (məsələn, field üçün) qadağandır. Onlar yalnız metod, konstruktor və s. içində işləyir.

5. Səhv emalı ilə birləşdir
Unutma ki, hətta using ilə də bütün exception-lar rahat deyil — oxuma/yazma səhvlərinə nəzarət lazımdırsa, try-catch əlavə etmək məntiqlidir.

Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION