如何通过底层Client模型高效访问ObservableCollection中的ClientViewModel?
我有一个绑定到ListView的ObservableCollection<ClientViewModel>,其中每个ClientViewModel是对Client模型的ViewModel包装类,包含仅用于UI的属性。
当前遇到的问题是:当Client触发连接/断开事件时,若要更新或移除集合中对应的ClientViewModel,我必须通过LINQ遍历集合,逐一检查ClientViewModel中的Client是否与触发事件的Client匹配,再进行修改。
请问是否存在更高效的实现方式?比如使用可实现O(1)访问的ObservableDictionary?
附带代码片段:
public ObservableCollection<ClientViewModel> ClientViewModels { get; } = new(); public void OnClientConnected(object sender, Client e) { ClientViewModels[...]? }
public class ClientViewModel : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; public Client Client { get; } private bool _online; public bool Online { get { return this._online; } set { this._online = value; this.NotifyPropertyChanged(nameof(Online)); } } public ClientViewModel(Client client) { Client = client; } public void NotifyPropertyChanged(string propertyName) { if (this.PropertyChanged != null) this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } }
1. 维护字典映射表(最简便的O(1)方案)
保留ObservableCollection用于UI绑定,同时新增一个Dictionary<Client, ClientViewModel>做快速映射,这样可以直接通过触发事件的Client实例O(1)找到对应的ViewModel,无需遍历集合。
示例代码:
public ObservableCollection<ClientViewModel> ClientViewModels { get; } = new(); private Dictionary<Client, ClientViewModel> _clientVmMap = new(); public void OnClientConnected(object sender, Client e) { if (!_clientVmMap.TryGetValue(e, out var vm)) { vm = new ClientViewModel(e); ClientViewModels.Add(vm); _clientVmMap.Add(e, vm); } vm.Online = true; } public void OnClientDisconnected(object sender, Client e) { if (_clientVmMap.TryGetValue(e, out var vm)) { vm.Online = false; // 如需移除ViewModel // ClientViewModels.Remove(vm); // _clientVmMap.Remove(e); } }
2. 自定义ObservableDictionary(直接绑定字典)
如果希望用字典类直接支持UI绑定,可以自定义实现INotifyCollectionChanged和INotifyPropertyChanged的ObservableDictionary,既保留字典的O(1)访问效率,又能像ObservableCollection一样通知UI更新。
简化版ObservableDictionary核心实现:
public class ObservableDictionary<TKey, TValue> : Dictionary<TKey, TValue>, INotifyCollectionChanged, INotifyPropertyChanged { public event NotifyCollectionChangedEventHandler CollectionChanged; public event PropertyChangedEventHandler PropertyChanged; public new void Add(TKey key, TValue value) { base.Add(key, value); CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, value)); OnPropertyChanged(nameof(Count)); OnPropertyChanged(nameof(Keys)); OnPropertyChanged(nameof(Values)); } public new bool Remove(TKey key) { if (base.TryGetValue(key, out var value) && base.Remove(key)) { CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, value)); OnPropertyChanged(nameof(Count)); OnPropertyChanged(nameof(Keys)); OnPropertyChanged(nameof(Values)); return true; } return false; } private void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
使用方式:
public ObservableDictionary<Client, ClientViewModel> ClientVmDictionary { get; } = new(); public void OnClientConnected(object sender, Client e) { if (!ClientVmDictionary.TryGetValue(e, out var vm)) { vm = new ClientViewModel(e); ClientVmDictionary.Add(e, vm); } vm.Online = true; }
UI绑定示例(XAML):
<ListView ItemsSource="{Binding ClientVmDictionary.Values}"> <!-- 自定义ItemTemplate --> </ListView>
3. 让ViewModel自主监听事件(逻辑内聚优化)
修改ClientViewModel,让它内部直接监听Client的连接/断开事件,状态更新逻辑完全封装在ViewModel内部,外部无需手动匹配和更新。
修改后的ClientViewModel:
public class ClientViewModel : INotifyPropertyChanged, IDisposable { public event PropertyChangedEventHandler PropertyChanged; public Client Client { get; } private bool _online; public bool Online { get { return _online; } set { _online = value; NotifyPropertyChanged(nameof(Online)); } } public ClientViewModel(Client client) { Client = client; // 假设Client存在Connected/Disconnected事件 client.Connected += OnClientConnected; client.Disconnected += OnClientDisconnected; // 初始化在线状态 Online = client.IsOnline; } private void OnClientConnected(object sender, EventArgs e) { Online = true; } private void OnClientDisconnected(object sender, EventArgs e) { Online = false; } public void Dispose() { // 释放事件订阅,避免内存泄漏 Client.Connected -= OnClientConnected; Client.Disconnected -= OnClientDisconnected; } public void NotifyPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
这种方式下,外部只需要负责将ClientViewModel添加到ObservableCollection,状态更新完全由ViewModel自动处理,进一步简化业务逻辑。
内容的提问来源于stack exchange,提问作者Meme Machine

