How to annotate nullability correctly for by-reference field setter?

60 Views Asked by At

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);
    }
0

There are 0 best solutions below