Skip to content

Instantly share code, notes, and snippets.

@jnm2
Last active June 26, 2024 21:00
Show Gist options
  • Save jnm2/196962b7f92c06a42e74b91f56867e72 to your computer and use it in GitHub Desktop.
Save jnm2/196962b7f92c06a42e74b91f56867e72 to your computer and use it in GitHub Desktop.

Nullability analysis with the field keyword

Properties that use the field keyword will not have warnings such as

For properties that have a get accessor with a body and which use field, analyze using this pseudocode, providing the same warnings as we would with accessors as local functions.

void Prop()
{
   T? field = default;

   // If there is an initializer:
   field = ...init-expr...

   // If there is a constructor assignment:
   set(...non-null-value...);

   while (true)
   {
      if (...non-constant-value...)
          get()
      else
          // If there truly is a setter on the property.  Otherwise, this is elided.
          set(...non-null-value...);
   }

   return;

   T get() { ...get-body... }
   void set(T value) { ...set-body... }
}
public C()
{
   T? field = default;

   // If there is an initializer:
   field = ...init-expr...

   ...ctor-statements, replacing property accessor calls with get/set local function calls...

   while (true)
   {
      if (...non-constant-value...)
          get()
      else
          // If there truly is a setter on the property.  Otherwise, this is elided.
          set(...non-null-value...);
   }

   return;

   T get() { ...get-body... }
   void set(T value) { ...set-body... }
}

Example:

class C : ViewModel
{
    // No warnings
    public C()
    {
        AutoProp = "";
        AutoGetterProp = "";
    }

    // Squiggle on 'C': Non-nullable property AutoProp must contain a non-null value when exiting the constructor.
    public C(int p) { }

    // No warnings
    public string AutoProp { get; set; }
    // Squiggle on 'get': Possible null reference return when the `C(int p)` constructor is used
    public string AutoGetterProp { get; set => Set(ref field, value); }
    // Squiggle on 'field': Possible null reference return when the `C(int p)` constructor is used
    public string ManualFieldProp { get => field; set => Set(ref field, value); }
    // No warnings
    public string InitializedProp { get => field; set => Set(ref field, value); } = "";
    // No warnings
    public required string RequiredProp { get => field; set => Set(ref field, value); }
}

abstract class ViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler? PropertyChanged;
    protected bool Set<T>(ref T field, T value, [CallerMemberName] string? propertyName = null);
}

Scenarios to consider

public string IntermediateStateProp { get => field; set => _myOldField = value; }
public string ConditionalProp { get => field; set { if (cond) field = value; } }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment