Understanding the Limitation of [MemberNotNull] on Static Methods

In C#, the [MemberNotNull] attribute is a powerful tool designed to assist the compiler's static nullability analysis. However, it has a key limitation: it only applies to the current instance (this).

When you apply [MemberNotNull] to a static method, the compiler expects the attribute to refer to static members of the class. It cannot associate the attribute with the properties of the newly created instance returned by the factory method. Consequently, the compiler still warns you with CS8602: Dereference of a possibly null reference when you access the property on the returned object.

While C# does not currently support an attribute like [return: MemberNotNull(...)], there are several elegant, idiomatic ways to resolve this warning without resorting to suppressing it.

Solution 1: Use a Private Constructor (Recommended)

The cleanest and most robust architectural solution is to avoid object initializers for mandatory properties. Instead, use a private constructor that accepts the required non-null value. This forces the compiler to recognize that the property is initialized during construction, satisfying the nullability flow analysis naturally.

public sealed class A
{
    public object? Obj { get; private set; }

    // Private constructor guarantees Obj is set
    private A(object obj)
    {
        Obj = obj;
    }

    // Static instantiator
    public static A MakeAWithObj() => new A(new object());

    public static void Test()
    { 
        A withObj = MakeAWithObj();
        // No warning! The compiler knows Obj is not null because of the constructor.
        int hash = withObj.Obj.GetHashCode(); 
    }
}

Solution 2: Leverage a Generic or Specific Subtype

If your class A can exist in two distinct states—one where Obj is null, and one where it is guaranteed to be present—you might be dealing with two different types. You can represent this using inheritance or generic parameters to enforce null-safety at the type level.

public class A
{
    public object? Obj { get; protected set; }
}

// A specialized version where Obj is guaranteed to be non-null
public sealed class AWithObj : A
{
    public new object Obj 
    {
        get => base.Obj!;
        private set => base.Obj = value;
    }

    public AWithObj(object obj)
    {
        Obj = obj;
    }
}

Solution 3: Use the Null-Forgiving Operator (!)

If you cannot change the class structure or constructor design, and you are absolutely certain that the static factory method always initializes the property, you can use the null-forgiving operator (!) to tell the compiler to trust your implementation.

public static void Test()
{
    A withObj = MakeAWithObj();
    // The ! operator suppresses the CS8602 warning
    int hash = withObj.Obj!.GetHashCode(); 
}

While this resolves the warning, use it sparingly as it bypasses compile-time safety checks. Whenever possible, prefer Solution 1 to let the type system enforce safety for you.