Bagikan melalui


Pola peristiwa lemah (WPF .NET)

Dalam aplikasi, ada kemungkinan bahwa handler yang melekat pada sumber peristiwa tidak akan dihancurkan dalam koordinasi dengan objek pendengar yang melampirkan handler ke sumber. Situasi ini dapat menyebabkan kebocoran memori. Windows Presentation Foundation (WPF) memperkenalkan pola desain yang dapat digunakan untuk mengatasi masalah ini. Pola desain menyediakan kelas manajer khusus untuk peristiwa tertentu, dan mengimplementasikan antarmuka pada pendengar untuk peristiwa tersebut. Pola desain ini dikenal sebagai pola peristiwa yang lemah.

Penting

Dokumentasi Panduan Desktop untuk .NET 7 dan .NET 6 sedang dibangun.

Prasyarat

Artikel ini mengasumsikan pengetahuan dasar tentang peristiwa yang dirutekan, dan anda telah membaca ringkasan peristiwa yang dirutekan. Untuk mengikuti contoh dalam artikel ini, ini membantu jika Anda terbiasa dengan Extensible Application Markup Language (XAML) dan tahu cara menulis aplikasi Windows Presentation Foundation (WPF).

Mengapa menerapkan pola peristiwa yang lemah?

Mendengarkan peristiwa dapat menyebabkan kebocoran memori. Teknik yang biasa untuk mendengarkan peristiwa adalah menggunakan sintaksis khusus bahasa untuk melampirkan handler ke peristiwa pada sumber. Misalnya, pernyataan source.SomeEvent += new SomeEventHandler(MyEventHandler) C# atau pernyataan AddHandler source.SomeEvent, AddressOf MyEventHandlerVB . Namun, teknik ini menciptakan referensi yang kuat dari sumber peristiwa ke pendengar peristiwa. Kecuali penanganan aktivitas secara eksplisit tidak terdaftar, masa pakai objek pendengar akan dipengaruhi oleh masa pakai objek sumber. Dalam keadaan tertentu, Anda mungkin ingin masa pakai objek pendengar dikontrol oleh faktor lain, seperti apakah saat ini milik pohon visual aplikasi. Setiap kali masa pakai objek sumber melampaui masa pakai objek yang berguna dari pendengar, pendengar tetap hidup lebih lama dari yang diperlukan. Dalam hal ini, memori yang tidak dialokasikan berjumlah kebocoran memori.

Pola peristiwa yang lemah dirancang untuk menyelesaikan masalah kebocoran memori. Pola peristiwa yang lemah dapat digunakan ketika pendengar perlu mendaftar untuk suatu acara, tetapi pendengar tidak secara eksplisit tahu kapan harus membatalkan pendaftaran. Pola peristiwa yang lemah juga dapat digunakan ketika masa pakai objek sumber melebihi masa pakai objek yang berguna dari pendengar. Dalam hal ini, berguna ditentukan oleh Anda. Pola peristiwa yang lemah memungkinkan pendengar untuk mendaftar dan menerima peristiwa tanpa memengaruhi karakteristik masa pakai objek pendengar dengan cara apa pun. Akibatnya, referensi tersirat dari sumber tidak menentukan apakah pendengar memenuhi syarat untuk pengumpulan sampah. Referensi adalah referensi yang lemah, sehingga penamaan pola peristiwa yang lemah dan API terkait. Pendengar dapat menjadi sampah yang dikumpulkan atau dihancurkan, dan sumber dapat dilanjutkan tanpa mempertahankan referensi handler yang tidak dapat dikoleksi ke objek yang sekarang hancur.

Siapa harus menerapkan pola peristiwa yang lemah?

Pola peristiwa yang lemah terutama relevan dengan penulis kontrol. Sebagai penulis kontrol, Anda sebagian besar bertanggung jawab atas perilaku dan penahanan kontrol Anda dan dampaknya pada aplikasi tempatnya dimasukkan. Ini termasuk perilaku masa pakai objek kontrol, khususnya penanganan masalah kebocoran memori yang dijelaskan.

Skenario tertentu secara inheren meminjamkan diri mereka ke penerapan pola peristiwa yang lemah. Salah satu skenario tersebut adalah pengikatan data. Dalam pengikatan data, umum bagi objek sumber untuk independen dari objek pendengar, yang merupakan target pengikatan. Banyak aspek pengikatan data WPF sudah memiliki pola peristiwa lemah yang diterapkan dalam bagaimana peristiwa diimplementasikan.

Cara Menerapkan pola peristiwa yang lemah

Ada empat cara untuk menerapkan pola peristiwa yang lemah, dan setiap pendekatan menggunakan manajer peristiwa yang berbeda. Pilih pengelola acara yang paling sesuai dengan skenario Anda.

  • Manajer peristiwa lemah yang ada:

    Gunakan kelas manajer peristiwa lemah yang ada saat peristiwa yang ingin Anda berlangganan memiliki WeakEventManager. Untuk daftar manajer peristiwa lemah yang disertakan dengan WPF, lihat hierarki warisan di WeakEventManager kelas . Karena manajer peristiwa lemah yang disertakan terbatas, Anda mungkin perlu memilih salah satu pendekatan lainnya.

  • Manajer peristiwa lemah generik:

    Gunakan generik WeakEventManager<TEventSource,TEventArgs> saat yang ada WeakEventManager tidak tersedia dan Anda mencari cara term mudah untuk mengimplementasikan peristiwa yang lemah. Namun, generik WeakEventManager<TEventSource,TEventArgs> kurang efisien daripada manajer peristiwa lemah yang ada atau kustom karena menggunakan refleksi untuk menemukan peristiwa dari namanya. Selain itu, kode yang diperlukan untuk mendaftarkan peristiwa menggunakan generik WeakEventManager<TEventSource,TEventArgs> lebih verbose daripada menggunakan yang ada atau kustom WeakEventManager.

  • Pengelola peristiwa lemah kustom:

    Buat kustom WeakEventManager saat yang ada WeakEventManager tidak tersedia dan efisiensi sangat penting. Meskipun lebih efisien daripada generik WeakEventManager, kustom WeakEventManager mengharuskan Anda menulis kode yang lebih di muka.

  • Pengelola peristiwa lemah pihak ketiga:

    Gunakan pengelola peristiwa lemah pihak ketiga saat Anda memerlukan fungsionalitas yang tidak disediakan oleh pendekatan lain. NuGet memiliki beberapa manajer peristiwa yang lemah. Banyak kerangka kerja WPF juga mendukung pola tersebut.

Bagian berikut menjelaskan cara menerapkan pola peristiwa yang lemah melalui penggunaan berbagai jenis manajer peristiwa. Untuk contoh manajer peristiwa umum dan lemah kustom, peristiwa untuk berlangganan memiliki karakteristik berikut.

  • Nama peristiwa adalah SomeEvent.
  • Acara ini dinaikkan oleh SomeEventSource kelas .
  • Penanganan aktivitas memiliki jenis EventHandler<SomeEventArgs>.
  • Peristiwa meneruskan parameter jenis SomeEventArgs ke penanganan aktivitas.

Menggunakan kelas manajer peristiwa lemah yang ada

  1. Temukan manajer peristiwa lemah yang ada. Untuk daftar manajer peristiwa lemah yang disertakan dengan WPF, lihat hierarki warisan WeakEventManager kelas.

  2. Gunakan manajer peristiwa lemah baru alih-alih hookup peristiwa normal.

    Misalnya, jika kode Anda menggunakan pola berikut untuk berlangganan peristiwa:

    source.LostFocus += new RoutedEventHandler(Source_LostFocus);
    
    AddHandler source.LostFocus, New RoutedEventHandler(AddressOf Source_LostFocus)
    

    Ubah ke pola berikut:

    LostFocusEventManager.AddHandler(source, Source_LostFocus);
    
    LostFocusEventManager.AddHandler(
        source, New EventHandler(Of RoutedEventArgs)(AddressOf Source_LostFocus))
    

    Demikian pula, jika kode Anda menggunakan pola berikut untuk berhenti berlangganan dari peristiwa:

    source.LostFocus -= new RoutedEventHandler(Source_LostFocus);
    
    RemoveHandler source.LostFocus, New RoutedEventHandler(AddressOf Source_LostFocus)
    

    Ubah ke pola berikut:

    LostFocusEventManager.RemoveHandler(source, Source_LostFocus);
    
    LostFocusEventManager.RemoveHandler(
        source, New EventHandler(Of RoutedEventArgs)(AddressOf Source_LostFocus))
    

Menggunakan kelas manajer peristiwa lemah generik

Gunakan kelas generik WeakEventManager<TEventSource,TEventArgs> alih-alih hookup peristiwa normal.

Saat Anda menggunakan WeakEventManager<TEventSource,TEventArgs> untuk mendaftarkan pendengar peristiwa, Anda menyediakan sumber peristiwa dan EventArgs mengetik sebagai parameter jenis ke kelas . Panggil AddHandler seperti yang ditunjukkan dalam kode berikut:

WeakEventManager<SomeEventSource, SomeEventArgs>.AddHandler(source, "SomeEvent", Source_SomeEvent);
WeakEventManager(Of SomeEventSource, SomeEventArgs).AddHandler(
    source, "SomeEvent", New EventHandler(Of SomeEventArgs)(AddressOf Source_SomeEvent))

Membuat kelas manajer peristiwa lemah kustom

  1. Salin templat kelas berikut ke proyek Anda. Kelas berikut mewarisi dari WeakEventManager kelas :

    class SomeEventWeakEventManager : WeakEventManager
    {
        private SomeEventWeakEventManager()
        {
        }
    
        /// <summary>
        /// Add a handler for the given source's event.
        /// </summary>
        public static void AddHandler(SomeEventSource source,
                                      EventHandler<SomeEventArgs> handler)
        {
            if (source == null)
                throw new ArgumentNullException(nameof(source));
            if (handler == null)
                throw new ArgumentNullException(nameof(handler));
    
            CurrentManager.ProtectedAddHandler(source, handler);
        }
    
        /// <summary>
        /// Remove a handler for the given source's event.
        /// </summary>
        public static void RemoveHandler(SomeEventSource source,
                                         EventHandler<SomeEventArgs> handler)
        {
            if (source == null)
                throw new ArgumentNullException(nameof(source));
            if (handler == null)
                throw new ArgumentNullException(nameof(handler));
    
            CurrentManager.ProtectedRemoveHandler(source, handler);
        }
    
        /// <summary>
        /// Get the event manager for the current thread.
        /// </summary>
        private static SomeEventWeakEventManager CurrentManager
        {
            get
            {
                Type managerType = typeof(SomeEventWeakEventManager);
                SomeEventWeakEventManager manager =
                    (SomeEventWeakEventManager)GetCurrentManager(managerType);
    
                // at first use, create and register a new manager
                if (manager == null)
                {
                    manager = new SomeEventWeakEventManager();
                    SetCurrentManager(managerType, manager);
                }
    
                return manager;
            }
        }
    
        /// <summary>
        /// Return a new list to hold listeners to the event.
        /// </summary>
        protected override ListenerList NewListenerList()
        {
            return new ListenerList<SomeEventArgs>();
        }
    
        /// <summary>
        /// Listen to the given source for the event.
        /// </summary>
        protected override void StartListening(object source)
        {
            SomeEventSource typedSource = (SomeEventSource)source;
            typedSource.SomeEvent += new EventHandler<SomeEventArgs>(OnSomeEvent);
        }
    
        /// <summary>
        /// Stop listening to the given source for the event.
        /// </summary>
        protected override void StopListening(object source)
        {
            SomeEventSource typedSource = (SomeEventSource)source;
            typedSource.SomeEvent -= new EventHandler<SomeEventArgs>(OnSomeEvent);
        }
    
        /// <summary>
        /// Event handler for the SomeEvent event.
        /// </summary>
        void OnSomeEvent(object sender, SomeEventArgs e)
        {
            DeliverEvent(sender, e);
        }
    }
    
    Class SomeEventWeakEventManager
        Inherits WeakEventManager
    
        Private Sub New()
        End Sub
    
        ''' <summary>
        ''' Add a handler for the given source's event.
        ''' </summary>
        Public Shared Sub [AddHandler](source As SomeEventSource,
                                       handler As EventHandler(Of SomeEventArgs))
            If source Is Nothing Then Throw New ArgumentNullException(NameOf(source))
            If handler Is Nothing Then Throw New ArgumentNullException(NameOf(handler))
            CurrentManager.ProtectedAddHandler(source, handler)
        End Sub
    
        ''' <summary>
        ''' Remove a handler for the given source's event.
        ''' </summary>
        Public Shared Sub [RemoveHandler](source As SomeEventSource,
                                          handler As EventHandler(Of SomeEventArgs))
            If source Is Nothing Then Throw New ArgumentNullException(NameOf(source))
            If handler Is Nothing Then Throw New ArgumentNullException(NameOf(handler))
            CurrentManager.ProtectedRemoveHandler(source, handler)
        End Sub
    
        ''' <summary>
        ''' Get the event manager for the current thread.
        ''' </summary>
        Private Shared ReadOnly Property CurrentManager As SomeEventWeakEventManager
            Get
                Dim managerType As Type = GetType(SomeEventWeakEventManager)
                Dim manager As SomeEventWeakEventManager =
                    CType(GetCurrentManager(managerType), SomeEventWeakEventManager)
    
                If manager Is Nothing Then
                    manager = New SomeEventWeakEventManager()
                    SetCurrentManager(managerType, manager)
                End If
    
                Return manager
            End Get
        End Property
    
        ''' <summary>
        ''' Return a new list to hold listeners to the event.
        ''' </summary>
        Protected Overrides Function NewListenerList() As ListenerList
            Return New ListenerList(Of SomeEventArgs)()
        End Function
    
        ''' <summary>
        ''' Listen to the given source for the event.
        ''' </summary>
        Protected Overrides Sub StartListening(source As Object)
            Dim typedSource As SomeEventSource = CType(source, SomeEventSource)
            AddHandler typedSource.SomeEvent, New EventHandler(Of SomeEventArgs)(AddressOf OnSomeEvent)
        End Sub
    
        ''' <summary>
        ''' Stop listening to the given source for the event.
        ''' </summary>
        Protected Overrides Sub StopListening(source As Object)
            Dim typedSource As SomeEventSource = CType(source, SomeEventSource)
            AddHandler typedSource.SomeEvent, New EventHandler(Of SomeEventArgs)(AddressOf OnSomeEvent)
        End Sub
    
        ''' <summary>
        ''' Event handler for the SomeEvent event.
        ''' </summary>
        Private Sub OnSomeEvent(sender As Object, e As SomeEventArgs)
            DeliverEvent(sender, e)
        End Sub
    End Class
    
  2. Ganti nama SomeEventWeakEventManager, SomeEvent, SomeEventSource, dan SomeEventArgs agar sesuai dengan nama peristiwa Anda.

  3. Atur pengubah akses untuk kelas pengelola peristiwa yang lemah agar sesuai dengan aksesibilitas acara yang dikelolanya.

  4. Gunakan manajer peristiwa lemah baru alih-alih hookup peristiwa normal.

    Misalnya, jika kode Anda menggunakan pola berikut untuk berlangganan peristiwa:

    source.SomeEvent += new EventHandler<SomeEventArgs>(Source_SomeEvent);
    
    AddHandler source.SomeEvent, New EventHandler(Of SomeEventArgs)(AddressOf Source_SomeEvent)
    

    Ubah ke pola berikut:

    SomeEventWeakEventManager.AddHandler(source, Source_SomeEvent);
    
    SomeEventWeakEventManager.AddHandler(
        source, New EventHandler(Of SomeEventArgs)(AddressOf Source_SomeEvent))
    

    Demikian pula, jika kode Anda menggunakan pola berikut untuk berhenti berlangganan peristiwa:

    source.SomeEvent -= new EventHandler<SomeEventArgs>(Source_SomeEvent);
    
    RemoveHandler source.SomeEvent, New EventHandler(Of SomeEventArgs)(AddressOf Source_SomeEvent)
    

    Ubah ke pola berikut:

    SomeEventWeakEventManager.RemoveHandler(source, Source_SomeEvent);
    
    SomeEventWeakEventManager.RemoveHandler(
        source, New EventHandler(Of SomeEventArgs)(AddressOf Source_SomeEvent))
    

Baca juga