You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

未知泛型类型时注册ObservableCollection的CollectionChanged事件方案

Implementation for Observing Property Changes (Including ObservableCollection Events)

Alright, let's tackle this problem. You want a base class that tracks all property changes, uses a dictionary backed by CallerMemberName for storage, and specifically handles ObservableCollection<T> by hooking into their CollectionChanged events (and unhooking old values to avoid memory leaks). Here's a complete, robust implementation:


1. Base Class Core Setup

First, we'll define the base class with the backing dictionary, basic GetValue<T>/SetValue<T> methods, and infrastructure to handle both property and collection changes.

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;

public class ObservableBase : INotifyPropertyChanged
{
    // Backing store for all properties, keyed by property name
    private readonly Dictionary<string, object> _backingFields = new Dictionary<string, object>();

    // Standard property changed event for UI binding or change tracking
    public event PropertyChangedEventHandler? PropertyChanged;

    // Maps collection instances to their parent property names (for triggering property updates on collection changes)
    private readonly Dictionary<object, string> _collectionPropertyMap = new Dictionary<object, string>();

    // Handler for collection changes - customize this logic to your needs
    private void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
    {
        if (sender != null && _collectionPropertyMap.TryGetValue(sender, out var propertyName))
        {
            // Trigger a property change for the parent property (useful for UI updates)
            OnPropertyChanged(propertyName);
            // Add custom logic here (e.g., validation, logging)
        }
    }

2. Implement RegisterIfObservable<T>

This method will handle attaching/detaching CollectionChanged events for any collection that implements INotifyCollectionChanged (which ObservableCollection<T> does). We'll build it to handle both new collection registration and old collection cleanup to prevent memory leaks.

/// <summary>
    /// Registers collection change events for observable collections, and cleans up old collection subscriptions
    /// </summary>
    /// <param name="newValue">New collection to observe</param>
    /// <param name="oldValue">Old collection to stop observing (optional)</param>
    /// <param name="propertyName">Name of the parent property (auto-populated via CallerMemberName)</param>
    public void RegisterIfObservable<T>(T newValue, T? oldValue = default, [CallerMemberName] string propertyName = "")
    {
        // Unhook old collection first to avoid memory leaks
        if (oldValue is INotifyCollectionChanged oldObservable)
        {
            oldObservable.CollectionChanged -= OnCollectionChanged;
            _collectionPropertyMap.Remove(oldObservable);
        }

        // Hook new collection if it's observable
        if (newValue is INotifyCollectionChanged newObservable)
        {
            newObservable.CollectionChanged += OnCollectionChanged;
            _collectionPropertyMap[newObservable] = propertyName;
        }
    }

3. Integrate with SetValue<T>

Update the SetValue<T> method to leverage RegisterIfObservable whenever a property is updated, passing both old and new values to handle event cleanup and registration automatically.

/// <summary>
    /// Retrieves a property value from the backing dictionary
    /// </summary>
    protected T GetValue<T>([CallerMemberName] string propertyName = "")
    {
        _backingFields.TryGetValue(propertyName, out var value);
        return value is T typedValue ? typedValue : default!;
    }

    /// <summary>
    /// Updates a property value, triggers property changed, and manages collection event subscriptions
    /// </summary>
    protected bool SetValue<T>(T newValue, [CallerMemberName] string propertyName = "")
    {
        var oldValue = GetValue<T>(propertyName);

        // Do nothing if values are equal
        if (EqualityComparer<T>.Default.Equals(oldValue, newValue))
        {
            return false;
        }

        // Update backing store
        _backingFields[propertyName] = newValue;

        // Handle collection event registration/unregistration
        RegisterIfObservable(newValue, oldValue, propertyName);

        // Trigger property changed notification
        OnPropertyChanged(propertyName);
        return true;
    }

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

4. Usage Example

Here's how you'd use this base class in a view model or data class:

public class TodoListViewModel : ObservableBase
{
    public ObservableCollection<TodoItem> TodoItems
    {
        get => GetValue<ObservableCollection<TodoItem>>();
        set => SetValue(value);
    }

    public string ListName
    {
        get => GetValue<string>();
        set => SetValue(value);
    }

    public TodoListViewModel()
    {
        TodoItems = new ObservableCollection<TodoItem>();
        ListName = "My Tasks";
    }
}

public class TodoItem
{
    public string Title { get; set; } = string.Empty;
    public bool IsCompleted { get; set; }
}

Key Notes

  • Memory Leak Prevention: We always unhook the old collection's event before hooking the new one, and remove old collections from our mapping dictionary. This ensures unused collections can be garbage collected properly.
  • Flexibility: By targeting INotifyCollectionChanged instead of just ObservableCollection<T>, this works with any custom collection that implements the interface.
  • UI-Friendly: The OnCollectionChanged handler triggers a property change for the parent property, which ensures UI bindings update when the collection's contents change (e.g., items added/removed).

内容的提问来源于stack exchange,提问作者J4N

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 10:01:59