I've got a fairly standard implementation of a base class to simplify implementation of INotifyPropertyChanged, which provides a standard implementation for raising the PropertyChanged event from property setters only if the new value is actually different from the previous value.
public abstract class PropertyChangeNotifierBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
protected void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = "")
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
}
public class PropertyChanger : PropertyChangeNotifierBase
{
private Foo _foo;
public Foo FooProperty
{
get => _foo;
set => SetField(ref _foo, value);
}
public PropertyChanger(Foo fooValue)
{
FooProperty = fooValue;
}
}
The compiler gives me warning CS8618 which complains that the PropertyChanger constructor doesn't set the value of the _foo field before it finishes. This looks like a false positive, since the constructor does set the wrapping property FooProperty, which calls into SetField(), which should set the value of _foo. Since the field and property are both typed as non-nullable Foo, shouldn't this guarantee that _foo is non-null after the property setter runs?
What annotations should I be adding to FooProperty and/or SetField<T> so that the property setting is properly analysed? Or, is the compiler's analysis correct, and I should instead be changing the behaviour of the code to ensure that _foo is initialised?
I tried adding [MemberNotNull(nameof(_foo))] to the PropertyChanger property. I thought that would inform the compiler that _foo will be non-null after the property is set, but that just changes the warnings. I instead get three warnings:
[MemberNotNull(nameof(_foo))]
public Foo FooProperty
{
// On _foo: CS8613, possible null reference return; CS8774 - Member must have a non-null value when exiting
get => _foo;
// On _foo: CS8610, possible null reference assignment
set => SetField(ref _foo, value);
}