首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >INotifyPropertyChanged奇异NullRefereneException

INotifyPropertyChanged奇异NullRefereneException
EN

Stack Overflow用户
提问于 2014-05-16 14:03:43
回答 2查看 531关注 0票数 1

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

代码语言:javascript
复制
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事件的侦听器,以便在更改子视图时使主视图模型“脏”。然而,问题是,当子视图被更改时,我会得到一个与父视图模型关联的空引用异常。

主视图模型(简化):

代码语言:javascript
复制
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();
        }
    }

子视图模型:

代码语言:javascript
复制
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();
        }
    }
}

因此,在我的单元测试中,我使用以下代码:

代码语言:javascript
复制
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);

这是堆栈跟踪:

代码语言:javascript
复制
Test Name:  DeleteDiaryDescriptionsById
Test FullName:  UnitTests.ViewModels.DiaryDescriptionViewModelTests.DeleteDiaryDescriptionsById
Test Source:    c:\Users\UnitTests\ViewModels\DiaryDescriptionViewModelTests.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:\Users\ViewModels\BaseChangeNotify.cs:line 70
   at ViewModels.BaseChangeNotify.set_IsDirty(Boolean value) in c:\Users\ViewModels\BaseChangeNotify.cs:line 43
   at ViewModels.BaseChangeNotify.OnPropertyChanged(String propertyName) in c:\Users\ViewModels\BaseChangeNotify.cs:line 57
   at ViewModels.DiaryDescriptionDetailsViewModel.set_Section(SectionViewModel value) in c:\Users\ViewModels\DiaryDescriptionDetailsViewModel.cs:line 158
   at ViewModels.DiaryDescriptionViewModel.set_SelectedDiaryDescription(DiaryDescriptionDetailsViewModel value) in c:\Users\ViewModels\DiaryDescriptionViewModel.cs:line 163
   at UnitTests.ViewModels.DiaryDescriptionViewModelTests.DeleteDiaryDescriptionsById() in c:\Users\UnitTests\ViewModels\DiaryDescriptionViewModelTests.cs:line 112

它似乎是在告诉我,与IsDirty关联的对象是null,这不是真。我通过调试器验证了它的存在,并取消了对DiaryDetailDescriptionViewModel.PropertyChanged事件寄存器的注释,它可以正常工作。我做错了吗?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2014-05-16 14:22:01

当从单元测试运行时,Application.Current是空的。

如果您想在单元测试中将内容安排到dispatcher,或者注入dispatcher本身,则需要将其抽象到某个接口后面。

票数 3
EN

Stack Overflow用户

发布于 2014-05-16 14:58:45

多亏了HenkGazTheDestroyer,我能够通过对GazTheDestroyer类的以下更改来解决这个问题。当从单元测试运行时,Application.Current对象始终为空,这就是导致NullReferenceException的原因。

代码语言:javascript
复制
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;
        }
    }
}
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/23697237

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档