INotifyPropertyChanged strange NullRefereneException



我有一个实现INotifyPropertyChanged的基类,我所有的视图模型都继承自:

public class BaseChangeNotify : INotifyPropertyChanged
{
    private bool isDirty;
    public BaseChangeNotify()
    {
    }
    public event PropertyChangedEventHandler PropertyChanged;
    public bool IsDirty
    {
        get
        {
            return this.isDirty;
        }
        set
        {
            this.isDirty = value;
            this.OnPropertyChanged();
        }
    }
    public virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
    {
        // Perform the IsDirty check so we don't get stuck in a infinite loop.
        if (propertyName != "IsDirty")
        {
            this.IsDirty = true; // Each time a property value is changed, we set the dirty bool.
        }
        if (this.PropertyChanged != null)
        {
            // Invoke the event handlers attached by other objects.
            try
            {
                Application.Current.Dispatcher.Invoke(() =>
                    this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName)));
            }
            catch (Exception exception)
            {
                throw exception;
            }
        }
    }
}

我有一个主视图模型,它实例子视图模型,从数据库包装模型。我注册为子视图模型PropertyChanged事件的侦听器,这样当子视图模型发生更改时,我就可以使主视图模型"脏"。然而,问题是,当子视图被更改时,我得到与父视图模型关联的空引用异常。

主视图模型(简化):

public class DiaryDescriptionViewModel : BaseViewModel, IDataErrorInfo
{
    private Diary diary;
    private ObservableCollection<DiaryDescriptionDetailsViewModel> diaryDescriptions;
    private DiaryDescriptionDetailsViewModel selectedDiaryDescription;
    private List<SectionViewModel> projectSections;
    public DiaryDescriptionViewModel()
    {
    }
    public DiaryDescriptionViewModel(Diary diary, UserViewModel user) : base(user)
    {
        this.diary = diary;
        // Restore any previously saved descriptions.
        var diaryRepository = new DiaryRepository();
        List<DiaryDescription> descriptions = diaryRepository.GetDiaryDescriptionsByDiaryId(diary.DiaryId);
        // Fetch sections for selected project.
        var projectSections = new List<Section>();
        projectSections = diaryRepository.GetSectionsByProjectId(diary.ProjectId);
        // Convert the Section model into a view model.
        this.projectSections = new List<SectionViewModel>(
            (from section in projectSections
             select new SectionViewModel(section))
                .ToList());
        foreach (var projectSection in this.projectSections)
        {
            // We want to set ourself to Dirty if any child View Model becomes dirty.
            projectSection.PropertyChanged += (sender, args) => this.IsDirty = true;
        }
        // Reconstruct our descriptions
        this.DiaryDescriptions = new ObservableCollection<DiaryDescriptionDetailsViewModel>();
        foreach (DiaryDescription description in descriptions)
        {
            SectionViewModel section =
                this.projectSections.FirstOrDefault(s => s.Items.Any(i => i.BidItemId == description.BidItemId));
            BidItem item = section.Items.FirstOrDefault(i => i.BidItemId == description.BidItemId);
            var details = new DiaryDescriptionDetailsViewModel(description, section, item);
            // Commenting this out resolves the NULL Reference Exception.
            details.PropertyChanged += (sender, args) => this.IsDirty = true;
            this.diaryDescriptions.Add(details);
        }
        this.IsDirty = false;
    }
    public ObservableCollection<DiaryDescriptionDetailsViewModel> DiaryDescriptions
    {
        get
        {
            return this.diaryDescriptions;
        }
        set
        {
            this.diaryDescriptions = value;
            this.OnPropertyChanged();
        }
    }
    public DiaryDescriptionDetailsViewModel SelectedDiaryDescription
    {
        get
        {
            return this.selectedDiaryDescription;
        }
        set
        {
            this.selectedDiaryDescription = value;
            if (value != null)
            {
                // If the description contains a biditem DiaryId, then we go fetch the section and biditem
                // associated with the diary description.
                if (value.BidItemId > 0)
                {
                    SectionViewModel sectionViewModel = this.ProjectSections.FirstOrDefault(
                        section => section.Items.FirstOrDefault(item => item.BidItemId == value.BidItemId) != null);
                    if (sectionViewModel != null)
                    {
                        BidItem bidItem = sectionViewModel.Items.FirstOrDefault(item => item.BidItemId == value.BidItemId);
                        this.selectedDiaryDescription.Section = sectionViewModel;
                        this.selectedDiaryDescription.BidItem = bidItem;
                    }
                }
                this.selectedDiaryDescription.IsDirty = false;
            }
            this.OnPropertyChanged();
            this.IsDirty = false;
        }
    }
    public List<SectionViewModel> ProjectSections
    {
        get
        {
            return this.projectSections;
        }
        set
        {
            this.projectSections = value;
            this.OnPropertyChanged();
        }
    }

子视图模型

public class DiaryDescriptionDetailsViewModel : BaseChangeNotify
{
    private readonly DiaryDescription diaryDescription;
    private SectionViewModel section;
    private BidItem bidItem;
    public DiaryDescriptionDetailsViewModel(DiaryDescription description)
    {
        this.diaryDescription = description;
        // If we have a valid biditem identifier (greater than 0) than we need to go and 
        // fetch the item and it's associated funding section.
        if (description.BidItemId > 0)
        {
            var repository = new DiaryRepository();
            this.section = new SectionViewModel(repository.GetSectionByBidItemId(description.BidItemId));
            this.bidItem = repository.GetBidItemById(description.BidItemId);
        }
        this.IsDirty = false;
    }
    public DiaryDescriptionDetailsViewModel(DiaryDescription description, SectionViewModel section, BidItem item)
    {
        this.diaryDescription = description;
        if (description.BidItemId > 0)
        {
            this.section = section;
            this.bidItem = item;
        }
        this.IsDirty = false;
    }
    public int Id
    {
        get
        {
            return this.diaryDescription.DiaryDescriptionId;
        }
    }
    public int DiaryId
    {
        get
        {
            return this.diaryDescription.DiaryId;
        }
    }
    public DiaryDescription Description
    {
        get
        {
            return this.diaryDescription;
        }
    }
    public int BidItemId
    {
        get
        {
            return this.diaryDescription.BidItemId;
        }
    }
    public BidItem BidItem
    {
        get
        {
            return this.bidItem;
        }
        set
        {
            this.bidItem = value;
            this.diaryDescription.BidItemId = value.BidItemId;
            this.OnPropertyChanged();
        }
    }
    public SectionViewModel Section
    {
        get
        {
            return this.section;
        }
        set
        {
            this.section = value;
            this.OnPropertyChanged();
        }
    }
}
所以在我的单元测试中,我使用以下代码:
var diaryRepository = new DiaryRepository();
Diary diary = diaryRepository.GetDiaryById(DiaryId);
var diaryDescriptionViewModel = new DiaryDescriptionViewModel(diary, new UserViewModel());
// Act
diaryDescriptionViewModel.SelectedDiaryDescription =
    diaryDescriptionViewModel.DiaryDescriptions.FirstOrDefault(
        desc => desc.Id == DiaryDescriptionId);

这是堆栈跟踪:

Test Name:  DeleteDiaryDescriptionsById
Test FullName:  UnitTests.ViewModels.DiaryDescriptionViewModelTests.DeleteDiaryDescriptionsById
Test Source:    c:UsersUnitTestsViewModelsDiaryDescriptionViewModelTests.cs : line 103
Test Outcome:   Failed
Test Duration:  0:00:02.678712
Result Message: 
Test method Pen.UnitTests.ViewModels.DiaryDescriptionViewModelTests.DeleteDiaryDescriptionsById threw exception: 
System.NullReferenceException: Object reference not set to an instance of an object.
Result StackTrace:  
at Pen.ViewModels.BaseChangeNotify.OnPropertyChanged(String propertyName) in c:UsersViewModelsBaseChangeNotify.cs:line 70
   at ViewModels.BaseChangeNotify.set_IsDirty(Boolean value) in c:UsersViewModelsBaseChangeNotify.cs:line 43
   at ViewModels.BaseChangeNotify.OnPropertyChanged(String propertyName) in c:UsersViewModelsBaseChangeNotify.cs:line 57
   at ViewModels.DiaryDescriptionDetailsViewModel.set_Section(SectionViewModel value) in c:UsersViewModelsDiaryDescriptionDetailsViewModel.cs:line 158
   at ViewModels.DiaryDescriptionViewModel.set_SelectedDiaryDescription(DiaryDescriptionDetailsViewModel value) in c:UsersViewModelsDiaryDescriptionViewModel.cs:line 163
   at UnitTests.ViewModels.DiaryDescriptionViewModelTests.DeleteDiaryDescriptionsById() in c:UsersUnitTestsViewModelsDiaryDescriptionViewModelTests.cs:line 112

它看起来像是在告诉我与IsDirty相关联的对象是空的,这不是真的。我通过调试器验证它存在并取消注释DiaryDetailDescriptionViewModel。PropertyChanged事件注册它工作正常。我做错了吗?

Application.Current在运行单元测试时为null。

如果你想在单元测试中调度调度程序,或者注入调度程序本身,你需要将它抽象到某个接口后面。

多亏了HenkGazTheDestroyer,我能够通过以下更改我的BaseChangeNotify类来解决这个问题。应用程序。当从单元测试中运行时,当前对象将始终为空,这就是导致NullReferenceException的原因。

public virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
    // Perform the IsDirty check so we don't get stuck in a infinite loop.
    if (propertyName != "IsDirty")
    {
        this.IsDirty = true; // Each time a property value is changed, we set the dirty bool.
    }
    if (this.PropertyChanged != null)
    {
        // Invoke the event handlers attached by other objects.
        try
        {
            // When unit testing, this will always be null.
            if (Application.Current != null)
            {
                Application.Current.Dispatcher.Invoke(() =>
                    this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName)));
            }
            else
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
        catch (Exception exception)
        {
            throw exception;
        }
    }
}

相关内容

  • 没有找到相关文章

最新更新