INotifyPropertyИзмененное странное NullRefereneException

1

У меня есть базовый класс, который реализует 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 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:\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, он отлично работает. Я делаю это неправильно?

Теги:
wpf
mvvm
nullreferenceexception
inotifypropertychanged

2 ответа

3
Лучший ответ

Application.Current имеет значение null при запуске от модульных тестов.

Вам нужно будет отвлечь его за некоторым интерфейсом, если вы хотите запланировать материал диспетчеру в модульных тестах или ввести сам диспетчер.

  • 0
    Спасибо, я только что проверил, будет ли диспетчер нулевым или нет. Я обновил свой источник, чтобы отразить это, что решило проблему. Никогда не замечал, что Application.Current будет нулевым.
1

Благодаря Henk и GazTheDestroyer я смог решить эту проблему со следующим изменением моего класса BaseChangeNotify. Объект Application.Current всегда будет иметь значение NULL при запуске от Unit Test, что и вызвало исключение 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;
        }
    }
}
  • 0
    Это ничего не добавляет к полезности оригинального ответа, и при этом это не ответ сам по себе. Ваша конкретная реализация вряд ли поможет кому-либо еще.
  • 1
    Первоначально я только что обновил свой OP с этим примером, и мне сказали публиковать его как отдельный ответ, чтобы соответствовать рекомендациям stackoverflow.
Показать ещё 3 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню