CodeGym /Kurslar /C# SELF /Praktik ssenarilər və ən yaxşı təcrübələr

Praktik ssenarilər və ən yaxşı təcrübələr

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

1. Eventlərin praktik istifadə ssenariləri

Eventlər — sadəcə dərsliklərdəki nəzəriyyə deyil. Praktikada onlar demək olar ki, hər ikinci tətbiqdə lazımdır, əgər UI və ya şəbəkə servisləri ilə işləyirsinizsə — əksər hallarda.

Gəlin real inkişafdan bir neçə tipik ssenarini nəzərdən keçirək. Eyni zamanda görək, necə standartlar və ən yaxşı təcrübələr ümumi səhvlərin qarşısını alır.

İstifadəçi hərəkətlərinə reaksiya (UI-da olduğu kimi)

Bəlkə də ən çox rast gəlinən ssenari — istifadəçi nəsə edir (düyməni basır, siyahıdan elementi seçir) və tətbiq reaksiya verməlidir. Windows Forms, WPF, UWP, Avalonia, MAUI və digər UI-çərçivələr belə işləyir.

Mini-nümunə (UI olmadan — düyməni imitasiya edirik):


public class Button
{
    public event EventHandler? Click; // standart delegat

    public void SimulateClick()
    {
        Click?.Invoke(this, EventArgs.Empty); // "Düymə" "basıldı"
    }
}

class Program
{
    static void Main()
    {
        var button = new Button();
        button.Click += (sender, e) => Console.WriteLine("Düymə basıldı!");
        button.SimulateClick(); // > Düymə basıldı!
    }
}

Real UI-çərçivədə Click eventi istifadəçi mausla basanda və ya ekrana tap edəndə işə düşür. Eventə qoşulan hər şey bildiriş alır — belə tətbiq loqikası işləyir.

Proqresin, əməliyyatın bitməsi və səhvlərin siqnalizasiyası

Təsəvvür edin: fayl yüklənməsi, məlumatların emalı, uzunmüddətli hesablamalar. Proqres və ya səhvlər barədə məlumat vermək lazımdır. Çox vaxt "proqres addımı" üçün event və ayrıca "bitdi" və ya "səhv" üçün event təşkil edirlər.

Fayl yükləyicisinin nümunəsi:


public class FileDownloader
{
    public event EventHandler<int>? ProgressChanged;
    public event EventHandler? DownloadCompleted;
    public event EventHandler<string>? DownloadFailed;

    public void Download()
    {
        for (int i = 1; i <= 100; i += 10)
        {
            Thread.Sleep(50); // gecikmənin imitasiya edilməsi
            ProgressChanged?.Invoke(this, i);
        }
        DownloadCompleted?.Invoke(this, EventArgs.Empty);
    }
}

var downloader = new FileDownloader();
downloader.ProgressChanged += (s, progress) => Console.WriteLine($"Yükləmə: {progress}%");
downloader.DownloadCompleted += (s, e) => Console.WriteLine("Yükləmə tamamlandı.");
downloader.Download();

Burada bir neçə event var: hamısına qoşula bilərsiniz (və ya yalnız lazım olanlara). Bu .NET-in fayl yükləmə, HTTP və s. üçün standart klasslarına oxşardır.

Obyektlərin həyat dövrünə reaksiya (məsələn, "saxlanıldı", "silindi")

Tutaq ki, sizin domen modeliniz var. İstəyirsiniz ki, istifadəçi obyekt saxlayanda və ya siləndə bəzi proseslər reaksiya versin: cache-i yeniləsin, log yazsın, mesaj göndərsin və s.


public class UserRepository
{
    public event EventHandler<UserEventArgs>? UserSaved;
    public event EventHandler<UserEventArgs>? UserDeleted;

    public void Save(User user)
    {
        //... istifadəçini saxlayırıq
        UserSaved?.Invoke(this, new UserEventArgs(user));
    }

    public void Delete(User user)
    {
        //... istifadəçini silirik
        UserDeleted?.Invoke(this, new UserEventArgs(user));
    }
}

public class UserEventArgs : EventArgs
{
    public User User { get; }
    public UserEventArgs(User user) => User = user;
}

İndi müxtəlif modullar qoşula və — birbaşa bağlılıq olmadan! — istifadəçilərin dəyişiklikləri barədə bildiriş ala bilərlər. Repository özü bilməyəcək kim "qoşulub".

Asinxron eventlər və çoxnövlülük (multithreading)

Bəzən eventlər əsas (UI) thread-dən deyil — fon tasklardan, timerlərdən və ya asinxron əməliyyatlardan gəlir. Belə hallarda unutmayın ki, handler kodu "başqasının" threadində işləyəcək. Handler UI elementlərini başqa thread-dən birbaşa yeniləməyə çalışsa — səhvlər alınacaq.

Marshalling nədir? Marshalling — kodun bir thread-dən digərinə ötürülməsidir, adətən fon thread-dən UI-thread-ə geri ötürmək üçün, UI-ni təhlükəsiz yeniləmək məqsədilə. UI tətbiqlərində (WinForms, WPF) bunun üçün SynchronizationContext və ya Dispatcher kimi mexanizmlərdən istifadə olunur ki, handler çağırışını düzgün threaddə "yönləndirsin".

Belə etməyin:


// Event fon thread-dən çağırılır, handler UI-ni birbaşa yeniləyir — istisna alacaqsınız!

Tövsiyə olunur: icra threadini yoxlayın və lazım gəldikdə UI-thread-ə marshal edin (SynchronizationContext və ya Dispatcher vasitəsilə). Konsol və server tətbiqlərində belə məhdudiyyətlər adətən yoxdur.

Event Aggregator / Messaging

Böyük tətbiqlərdə çox vaxt mərkəzləşdirilmiş event aggregator istifadə olunur. Bu bağlılığı azaldır: subscriber və publisher-lər bir-birini tanımadan mərkəz vasitəsilə mesajlaşırlar.


public class EventAggregator
{
    public event EventHandler<SomeEventArgs>? SomeEvent;

    public void Publish(SomeEventArgs args)
    {
        SomeEvent?.Invoke(this, args);
    }
}

2. Ən yaxşı patternlər və təcrübələr

[CallerMemberName] atributundan istifadə edin

İnfrastruktur kodu yazanda (məsələn, logger, tracer) eventin mənbə metodu adını CallerMemberName atributu ilə log-lamaq diaqnostikanı asanlaşdırır.

Tələb olunarsa eventləri thread-safe edin

Eventlər multicast olur və müxtəlif threadlərdən müraciətlər diqqət tələb edir: çağırmadan əvvəl delegatı lokala köçürün.


var handler = MyEvent;
if (handler != null) handler(this, args);

C# 6+ istifadə edirsinizsə, təhlükəsiz çağırışdan istifadə edin: MyEvent?.Invoke(this, args).

Custom EventArgs-ı immutable edin

Öz EventArgs tiplərinizi yalnız oxuna bilən (read-only) propert-lərlə müəyyən edin — bu subscriber-lərin vəziyyəti təsadüfən dəyişdirməsinin qarşısını alır.


public class WorkCompletedEventArgs : EventArgs
{
    public string Message { get; }
    public WorkCompletedEventArgs(string message) => Message = message;
}

public yoxsa private event

Eventləri public event kimi edin, amma çağırışı protected virtual OnEventName metodunda inkapsulyasiya edin. Bu genişlənməyə imkan verir (törəmə class onlar davranışı override edə bilər) və xarici istifadəçilər eventi birbaşa çağıra bilməz.

Həyat dövrü ardıcıllığını pozmayın

Əgər uzun zəncirli handlerlər başladırsınızsa, unutmayın: kimsə qoşula və daxili loqikanı dəyişməyə çalışa bilər. Harada və nə vaxt eventlər çağırıldığını sənədləşdirin və eventin bir neçə dəfə çağırıla biləcəyini göstərin.

Bir neçə event və kompozisiya

Bir obyekt bir neçə mənbəni dinləyirsə, subscribe və unsubscribe əməliyyatlarını bir yerdə qruplaşdırın (məsələn, Subscribe/Unsubscribe metodları). Lazım gəldikdə privat field-delegatları saxlayın unsubscribe üçün.

3. Eventlərin necə istifadə olunmamalı: tipik səhvlər

Səhv №1: standart event patternini görməzlikdən gəlmək.
Hər klass öz delegatlarını hər event üçün yaratsaydı, bu tezliklə xaosa çevrilər. Mümkün qədər standart delegatlar EventHandler və ya EventHandler<T> istifadə edin, burada T EventArgs-dən törəməlidir. Bu yanaşma kodu .NET inkişaf etdiriciləri üçün aydın və tanış edir.

Səhv №2: eventi xarici koddən səhv çağırmaq.
Heç vaxt eventi direkt olaraq publisher xaricində çağırmayın. Event yalnız klass daxilində initiate olunmalıdır, adətən qorunan OnEventName metodu vasitəsilə. Subscriber-lər sadəcə qoşulur (+=) və ayrılır (-=).

Pis nümunə:


foo.MyEvent(); // səhv! Eventi xaricdən birbaşa çağırmaq olmaz

Yaxşı nümunə:


// Yalnız foo daxilində:
// protected virtual void OnMyEvent()
// {
//     MyEvent?.Invoke(this, EventArgs.Empty);
// }

Səhv №3: subscriber yoxlanışını (null) etmədən eventi çağırmaq.
Əgər eventə heç kim qoşulmayıbsa və onu çağırmağa çalışsanız, NullReferenceException ala bilərsiniz. C# 6.0+-da ?.Invoke(...) istifadə edin — event yalnız subscriber varsa çağırılacaq.

Səhv №4: eventdən ayrılmağı unutmaq (memory leak).
Əgər obyekt eventə qoşulub və məhv olmadan əvvəl unsubscribe etməyibsə, publisher subscriber-ə referans saxlayır. Garbage collector bu obyekti azad edə bilməz, bu uzunömürlü publisher-lər və qısamüddətli subscriber-lər (məsələn, ViewModel, form) üçün kritik ola bilər. Ən yaxşı həlldir — açıq unsubscribe və ya WeakReference istifadə etmək, lazım olanda.

Səhv №5: eventi qorunan virtual OnEvent metodu xaricində çağırmaq.
Eventi birbaşa çağırmaq törəmə siniflərin override etmə imkanını məhv edir. Düzgün pattern — protected virtual metod elan edib onun içində eventi çağırmaq.

Standart nümunə:


protected virtual void OnSomething(EventArgs e)
{
    Something?.Invoke(this, e);
}
2
Tapşırıq
C# SELF, səviyyə, dərs
Bağlanıb
Obyektlərin həyat dövrünün idarə olunması event-lər vasitəsilə
Obyektlərin həyat dövrünün idarə olunması event-lər vasitəsilə
1
Sorğu/viktorina
, səviyyə, dərs
Əlçatan deyil
Hadisələrin həyat dövrü
Hadisələrə abunə olmanın detallı təhlili
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION