Sweeper
Sweeper

Reputation: 270733

How to make C# aware of the nullability of convenient properties?

Consider this code snippet, which has no nullable warnings.

public class Foo {
    public string Property { get; } = "Some String";
}

public class Bar {
    [DisallowNull]
    public Foo? Foo { get; private set; }
    
    [MemberNotNull(nameof(Foo))]
    public void MyMethod() {
        Foo = new Foo();
        
        // After setting Foo, I use Foo.Property in some way
        Console.WriteLine(Foo.Property);
    }
}

Since in my real code, I use Foo.Property after setting Foo a lot, I wanted to add a "convenient property" to Bar that returns it directly, so that I can access it with a shorter name (Foo is actually a rather long name in my real code):

// in Bar
public string? Property => Foo?.Property;

// ...
// Now I can do:
Console.WriteLine(Property);

However, now Bar.Property is nullable, even in a place where Foo is definitely not null (such as just after setting Foo). So when I use Property in a place where null is not allowed, the compiler gives me warnings.

I thought what I needed is to annotate Property with something like NotNullIfMemberNotNull(nameof(Foo)), but after looking it up, this attribute is only proposed, and doesn't exist yet.

How can I work around this?

Upvotes: 4

Views: 422

Answers (2)

Drew Noakes
Drew Noakes

Reputation: 310792

Firstly, MemberNotNull is a post condition that only signals to callers that the specified member will not be null after the member returns. It will not help you within the method itself unfortunately.

I understand you're using a simplification of a larger pattern, but one way I would suggest rewriting MyMethod is:

public void MyMethod() {
    var foo = new Foo();
    Foo = foo;
    Console.WriteLine(foo.Property);
}

Upvotes: 1

Sweeper
Sweeper

Reputation: 270733

One workaround I came up with is make both Foo and Property have backing fields. In the setter of Foo, also set Property. This way, we can add the MemberNotNull attribute to the setter of Foo.

private Foo? foo;

[DisallowNull]
public Foo? Foo { 
    get => foo;
    [MemberNotNull(nameof(Property))]
    private set {
        foo = value;
        Property = value.Property;
    }
}

public string? Property { get; private set; }

However, this isn't very general. It only works because Foo just happens to be marked as DisallowNull.

Upvotes: 2

Related Questions