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

WPF C#战舰游戏点击单元格触发INotifyPropertyChanged空引用异常

问题分析与解决方案

核心错误点1:ViewModelBase未处理PropertyChanged事件为空的情况

你的ViewModelBase类中,Set和Fire方法直接调用PropertyChanged事件,未判断事件是否为null,当没有订阅者时会直接抛出NullReferenceException。

修复代码:

internal class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
   
    protected void Set<T>(ref T field, T value, [CallerMemberName] string propName = "")
    {
        if (!EqualityComparer<T>.Default.Equals(field, value))
        {
            field = value;
            // 使用空条件运算符避免空引用
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
        }
    }

    protected void Fire(params string[] names)
    {
        foreach (var name in names)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }
    }
}

注:改用EqualityComparer<T>.Default.Equals避免字段为null时调用Equals引发的额外空引用。

核心错误点2:MainWindow构造函数中DataContext实例不一致

你先将DataContext赋值给初始的bs,随后又重新实例化BattleshipVM覆盖bs,导致界面绑定的是旧实例,后续操作的却是新实例,引发逻辑混乱甚至绑定异常。

修复代码:

调整构造函数顺序,保证DataContext指向正确的实例:

public MainWindow()
{
    InitializeComponent();
    bs = new BattleshipVM();
    DataContext = bs;
}

或直接使用字段声明时初始化的实例:

public MainWindow()
{
    InitializeComponent();
    DataContext = bs;
}

核心错误点3:CellModel中Miss和Shot共用同一字段

CellModel里Miss和Shot属性绑定到同一个visibility字段,设置其中一个属性时会触发另一个属性的绑定,导致状态完全混乱,也可能引发绑定层面的异常。

修复代码:

为两个属性分别设置独立字段:

public class CellModel : ViewModelBase
{
    Visibility _missVisibility = Visibility.Collapsed;
    Visibility _shotVisibility = Visibility.Collapsed;
    bool _isShip;

    public CellModel(char state)
    {
        _isShip = state == 'X';
    }

    public Visibility Miss 
    { 
        get => _missVisibility; 
        private set => Set(ref _missVisibility, value); 
    }

    public Visibility Shot 
    { 
        get => _shotVisibility; 
        private set => Set(ref _shotVisibility, value); 
    }

    public void SetState()
    {
        if (_isShip)
            Shot = Visibility.Visible;
        else
            Miss = Visibility.Visible;
    }
}

额外优化:MapFabric方法的字符串分割逻辑

原始代码用空格分割地图字符串,会因每行前的空格导致分割结果异常,改为按换行符分割并清理空白:

CellModel[][] MapFabric(string str)
{
    // 按换行分割,过滤空行并清理每行前后空格
    var mp = str.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
                .Select(line => line.Trim())
                .ToArray();
                
    var map = new CellModel[10][];
    for (int i = 0; i < 10; i++)
    {
        map[i] = new CellModel[10];
        for (int j = 0; j < 10; j++)
        {
            map[i][j] = new CellModel(mp[i][j]);
        }
    }
    return map;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 07:00:58