CodeGym /Kurslar /C# SELF /Hadisələrdən unsubscribe ( -...

Hadisələrdən unsubscribe ( -= ) və yaddaş sızmaları

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

1. Giriş

Bir növ, C#-da hadisəyə abonə olmaq dostdan meme göndərmə siyahısına qoşulmaq kimidir: xəbərdarlıqlar gəlir, siz "kifayətdir" deməyincə və unsubscribe etməyincə dayanmaz. Proqramlaşdırmada bu xüsusilə əhəmiyyətlidir, çünki unudulmuş abonəlik sadəcə "daha bir meme" deyil, yaddaş sızması deməkdir!

Təsəvvür edin ki, tətbiqdə əlavə bir forma (məsələn, ayar pəncərəsi) var. O, əsas pəncərənin hadisəsinə abunə olur ki, dəyişikliklərə reaksiya versin. İstifadəçi formanı bağlayır, elə bilir ki, form məhv edildi, amma handler hələ də abunədir! Forma yaddaşda qalır, çünki publisher hadisə vasitəsilə ona istinad saxlayır.

Nəticə: Əgər subscriber obyekt publisher-in hadisəsinə abunə olub və unsubscribe etməyi "unudubsa", collector onu yaddaşdan silməyəcək, publisher sağ olduğu müddətdə.

Operatorları +=-= xatırlayaq

  • += — abonəlik: event çağırışları siyahısına handler əlavə edir.
  • -= — unsubscribe: handler-i çağırışlar siyahısından çıxarır.

Bu təxminən belə görünür:


worker.WorkCompleted += handler; // abonəlik
worker.WorkCompleted -= handler; // unsubscribe

Əgər handler iki dəfə əlavə edilibsə, onu siyahıdan tamamilə çıxarmaq üçün eyni sayda silmək lazımdır.

Bəzi daxili məqamlar

Səhnə arxasında C#-da event sahə-delegate (və ya delegate siyahısı)dır və operator += faktiki olaraq Delegate.Combine-i çağırır, -= isə Delegate.Remove-i. Abonə olan obyekt referanslar qrafının bir hissəsinə çevrilir. Odur ki, unudulmuş abonəlik = yaddaş sızması.

2. Hadisələr vasitəsilə yaddaş sızmaları: necə işləyir

Klassik vəziyyət


class Window
{
    public event EventHandler Updated;

    public void SimulateUpdate()
    {
        // Təqlid: bütün abonələrə xəbər veririk
        Updated?.Invoke(this, EventArgs.Empty);
    }
}

class SettingsForm
{
    public void OnWindowUpdated(object sender, EventArgs e)
    {
        Console.WriteLine("SettingsForm pəncərə yenilənməsinə reaksiya verir");
    }
}

Gəlin addım-addım:


var window = new Window();
var settingsForm = new SettingsForm();

window.Updated += settingsForm.OnWindowUpdated;

window.SimulateUpdate(); // SettingsForm reaksiya verir

// İstifadəçi formanı bağladı. Ona olan bütün istinadları itiririk:
settingsForm = null;

// Amma SettingsForm obyekti garbage collector tərəfindən silinməyəcək, window sağ olduğu müddətcə,
// çünki window.Updated hələ də OnWindowUpdated metoduna istinad saxlayır,
// yəni SettingsForm obyektinə də.

Nə etmək lazımdır?
Unsubscribe etmək:


// Bunun üçün bizim handler-ə və ya obyektə istinadımız qalmalıdır:
window.Updated -= settingsForm.OnWindowUpdated;
settingsForm = null; // İndi obyekt silinə bilər

Kim kimin üzərində istinad saxlayır — cədvəl

Əməl Kimin üzərində istinad saxlanılır Yaddaş azad etmək mümkündür?
Hadisəyə abonəlik (+=) Publisher → Subscriber Xeyr, publisher sağ olduğu müddətcə
Unsubscribe (-=) Yox Bəli, bütün xarici istinadlar silindikdən sonra
Abonəlik yox Yox Bəli

3. Unsubscribe-i necə düzgün təşkil etmək

Handler-i açıq şəkildə silmək

Bunu, məsələn, pəncərə və ya formanı bağladıqda etmək olar:


class SettingsForm
{
    private readonly Window _window;

    public SettingsForm(Window window)
    {
        _window = window;
        _window.Updated += OnWindowUpdated;
    }

    public void Close()
    {
        _window.Updated -= OnWindowUpdated; // unsubscribe edirik!
        // burada bağlanma üçün kod (məsələn, Dispose, GC.SuppressFinalize və s.)
    }

    public void OnWindowUpdated(object sender, EventArgs e)
    {
        // Hadisə emalı
    }
}

Əgər SettingsForm "bağla" düyməsi ilə məhv olunursa, unsubscribe edən metodu (məsələn, Close()) çağırmağı unutmaq olmaz.

IDisposable interfeysindən istifadə

Hadisələrə abunə olan və öz həyat dövrünü idarə edən mürəkkəb obyektlər üçün IDisposable interfeysini implementasiya etmək rahatdır. Dispose() metodunda bütün lazım olan unsubscribe əməliyyatları aparılır.


class SettingsForm : IDisposable
{
    private readonly Window _window;

    public SettingsForm(Window window)
    {
        _window = window;
        _window.Updated += OnWindowUpdated;
    }

    public void OnWindowUpdated(object sender, EventArgs e)
    {
        // ...
    }

    public void Dispose()
    {
        _window.Updated -= OnWindowUpdated;
        // Burada digər resursları da azad edirik
    }
}

İndi SettingsForm-u using bloku içində istifadə etmək, əl ilə Dispose() çağırmaq və ya finalizasiya olunan tiplərdə GC.SuppressFinalize-dən istifadə etməklə resurs azad etməni avtomatlaşdıra bilərsiniz.

4. Lambda ifadələri ilə qarşılıqlı əlaqə: təhlükələr və hiylələr

Əgər hadisəyə lambda ifadəsi ilə abunə olursunuz və lambdanı dəyişəndə saxlamırsınızsa, ona unsubscribe edə bilməzsiniz!


// Abonəlik — anonim lambda
window.Updated += (s, e) => Console.WriteLine("Lambda çağırıldı!");

// İndi necə unsubscribe etmək olar? — Heç cür!
window.Updated -= (s, e) => Console.WriteLine("Lambda çağırıldı!"); // Bu başqa delegate-dir!

Nə etmək lazımdır?
Lambdanı delegate dəyişənində saxlayın:


EventHandler handler = (s, e) => Console.WriteLine("Lambda çağırıldı!");
window.Updated += handler;

// ... indi unsubscribe etmək olar!
window.Updated -= handler;

5. Faydalı nüanslar

Objektlərin həyat dövrü və hadisələrin xüsusiyyətləri

Başqa bir tez-tez rastlanan problem — iki "uzunömürlü" obyekt arasında hadisələr vasitəsilə qarşılıqlı istinadlar. Məsələn, bir pəncərə digərinin hadisəsinə abunə olur, hər ikisi aktiv istifadə olunur, silinmir — və yaddaş tədricən artır.

Tövsiyə: Həmişə yadda saxlayın ki, kim kimin üzərinə abunə olur və nə vaxt unsubscribe edilməlidir. Əgər abonəlik publisher ilə eyni həyat dövrünə malikdirsə — problem yoxdur. Əgər subscriber publisher-dan daha qısamüddətli ola bilərsə, açıq unsubscribe implementasiya edin.

Ümumi qayda: "Abonə olsan — unsubscribe et!"

  • Uzunömürlü publisher-lar (məsələn, qlobal, singleton, əsas pəncərələr) üçün — subscriber-lərdə həmişə unsubscribe implementasiya edin.
  • Müvəqqəti obyektlər üçün (məsələn, bir dəfəlik bildirişlər və ya abonəlikdə subscriber publisher-dan daha uzun yaşayırsa) — bir qədər rahat olmaq olar, amma konteksti izləyin.
  • Əl ilə unsubscribe etmək istəmirsinizsə, WeakEvent (səbəbləri zəif olan hadisələr) və ya xüsusi framework-lərdən istifadə edin.

6. Unsubscribe ilə bağlı tipik səhvlər

Uğursuz unsubscribe: handler eyni olmalıdır

Unsubscribe edərkən abunə olarkən istifadə etdiyiniz eyni handler-i göstərmək çox vacibdir. Əks halda unsubscribe işə yaramayacaq.

Səhv:


window.Updated += settingsForm.OnWindowUpdated;
// ...
window.Updated -= new SettingsForm().OnWindowUpdated; // İşləməyəcək! Bu başqa obyektin və başqa delegate-in nümunəsidir!

Düzgün:


window.Updated -= settingsForm.OnWindowUpdated;

Əgər abunəlik anonim lambda ilə baş veribsə və delegate-ə istinad saxlanmayıbsa, ondan unsubscribe etmək mümkün deyil, çünki bu artıq başqa delegate nümunəsi olacaq:


// Abonəlik
window.Updated += (s, e) => Console.WriteLine("Lambda!");

// Unsubscribe cəhdi — işləməyəcək!
window.Updated -= (s, e) => Console.WriteLine("Lambda!");

"Unudulmuş" unsubscribe

Çox vaxt unsubscribe sadəcə unudulur, xüsusilə subscriber publisher-dan daha uzun yaşayırsa və ya developer hadisələrin mexanizmini tam başa düşmürsə. Nəticədə abonə obyektlər gözləniləndən daha uzun müddət yaddaşda qalır, bu da yaddaş sızmalarına və performans problemlərinə gətirib çıxarır.

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