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

ViewModel中RelayCommand继承引发SaveCommand空引用异常问题

主/详细视图模型中RelayCommand继承导致的空引用异常解决

问题场景

设计主/详细视图模型架构时,创建了包含Add、Edit、Save、Cancel、Delete命令及CanExecute逻辑的基类。其中Save和Delete命令需要在派生类中扩展实现,但希望保留基类的CanExecute管理逻辑。采用示例代码实现时,NotifyButtonsState()方法中调用SaveCommand.NotifyCanExecuteChanged()和DeleteCommand.NotifyCanExecuteChanged()会触发System.NullReferenceException: 对象引用未设置到对象的实例错误。

基类代码

internal abstract partial class MasterDetailViewModelBase<TDetailViewModel> : ObservableObject
{
    private TDetailViewModel? currentItem;
    public TDetailViewModel? CurrentItem
    {
        get => currentItem;
        set
        {
            if (EqualityComparer<TDetailViewModel?>.Default.Equals(currentItem, value)) return;
            SetProperty(ref currentItem, value);
            OnPropertyChanged(nameof(HasCurrentItem));
            NotifyButtonsState();
        }
    }

    public bool HasCurrentItem => CurrentItem is not null;

    private void NotifyButtonsState()
    {
        OnPropertyChanged(nameof(CanAdd));
        OnPropertyChanged(nameof(CanEdit));
        OnPropertyChanged(nameof(CanSave));
        OnPropertyChanged(nameof(CanCancel));
        OnPropertyChanged(nameof(CanDelete));
        AddCommand.NotifyCanExecuteChanged();
        EditCommand.NotifyCanExecuteChanged();
        SaveCommand.NotifyCanExecuteChanged();     // <-- 错误位置
        CancelCommand.NotifyCanExecuteChanged();
        DeleteCommand.NotifyCanExecuteChanged();
    }

    public bool CanAdd => HasCurrentItem ? CurrentItem.State == ItemState.Unchanged : true;
    public bool CanEdit => HasCurrentItem ? CurrentItem.State == ItemState.Unchanged : false;
    public bool CanSave => HasCurrentItem ? CurrentItem.IsChanged : false;
    public bool CanCancel => HasCurrentItem
                                  ? CurrentItem.State == ItemState.Adding ||
                                    CurrentItem.State == ItemState.Editing
                                  : false;
    public bool CanDelete => HasCurrentItem ? CurrentItem.State == ItemState.Unchanged : false;

    [RelayCommand(CanExecute = nameof(CanAdd))]
    private void Add()
    {
        // [...]
    }

    [RelayCommand(CanExecute = nameof(CanEdit))]
    private void Edit()
    {
        // [...]
    }

    // CommunityToolkit.Mvvm实现RelayCommand特性的方式示例
    protected IRelayCommand SaveCommand { get; }
    protected void Save()
    {
        // [...]
    }

    [RelayCommand(CanExecute = nameof(CanCancel))]
    private void Cancel()
    {
        // [...]
    }

    protected IRelayCommand DeleteCommand { get; }
    protected void Delete()
    {
        // [...]
    }
}

派生类代码

internal partial class CustomersViewModel : MasterDetailViewModelBase<CustomerViewModel>
{
    [RelayCommand(CanExecute = nameof(CanSave))]
    private void Save()
    {
        // [...]
        base.Save();
    }

    [RelayCommand(CanExecute = nameof(CanDelete))]
    private void Delete()
    {
        // [...]
        base.Delete();
    }
}

错误原因

基类中仅声明了SaveCommand和DeleteCommand属性,但未对其进行实例化。派生类通过[RelayCommand]特性生成的命令是派生类自己的私有字段,不会赋值给基类的SaveCommand/DeleteCommand属性,导致基类中这两个属性始终为null,调用NotifyCanExecuteChanged()时触发空引用异常。

解决方案

方案1:基类定义虚方法并添加[RelayCommand],派生类重写方法

这种方式让基类负责命令的实例化,派生类仅扩展方法逻辑,保证基类的SaveCommand/DeleteCommand有有效实例:

修改后的基类代码

internal abstract partial class MasterDetailViewModelBase<TDetailViewModel> : ObservableObject
{
    // ... 其他代码保持不变 ...

    [RelayCommand(CanExecute = nameof(CanSave))]
    protected virtual void Save()
    {
        // 基类的Save逻辑
        // [...]
    }

    [RelayCommand(CanExecute = nameof(CanDelete))]
    protected virtual void Delete()
    {
        // 基类的Delete逻辑
        // [...]
    }
}

修改后的派生类代码

internal partial class CustomersViewModel : MasterDetailViewModelBase<CustomerViewModel>
{
    protected override void Save()
    {
        // 派生类扩展的Save逻辑
        // [...]
        base.Save();
    }

    protected override void Delete()
    {
        // 派生类扩展的Delete逻辑
        // [...]
        base.Delete();
    }
}

方案2:基类通过抽象属性获取派生类的命令实例

如果必须在派生类中用[RelayCommand]特性生成命令,可在基类中定义抽象属性,让派生类返回自己的命令实例:

修改后的基类代码

internal abstract partial class MasterDetailViewModelBase<TDetailViewModel> : ObservableObject
{
    // ... 其他代码保持不变 ...

    // 替换原来的SaveCommand/DeleteCommand属性为抽象属性
    protected abstract IRelayCommand SaveCommand { get; }
    protected abstract IRelayCommand DeleteCommand { get; }

    protected void Save()
    {
        // 基类的Save逻辑
        // [...]
    }

    protected void Delete()
    {
        // 基类的Delete逻辑
        // [...]
    }
}

修改后的派生类代码

internal partial class CustomersViewModel : MasterDetailViewModelBase<CustomerViewModel>
{
    [RelayCommand(CanExecute = nameof(CanSave))]
    private void Save()
    {
        // [...]
        base.Save();
    }

    [RelayCommand(CanExecute = nameof(CanDelete))]
    private void Delete()
    {
        // [...]
        base.Delete();
    }

    // 实现基类的抽象属性,返回自动生成的命令实例
    protected override IRelayCommand SaveCommand => saveCommand;
    protected override IRelayCommand DeleteCommand => deleteCommand;
}

注意:CommunityToolkit.Mvvm的[RelayCommand]特性会自动生成以下划线开头的私有命令字段(比如saveCommand),派生类可直接引用该字段。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 23:16:03