Is it possible to inform the C# compiler that another property will be non-null based on another property?

Viewed 218

Supposing I have a class like this:

public class BridgeFormModel
{
    [Required]
    [Display( Name = "What is your name?" )]
    public String? Name { get; set; }

    [Required]
    [Display( Name = "What is your quest?" )]
    public String? Quest { get; set; }

    [Required]
    [Display( Name = "What is your favourite colour?" )]
    public String? FaveColour { get; set; }

    [BindNever]
    public Boolean IsValid =>
        !String.IsNullOrWhiteSpace( this.Name ) && 
        !String.IsNullOrWhiteSpace( this.Quest ) && 
        !String.IsNullOrWhiteSpace( this.FaveColour );
}

Currently the C# 8.0 and C# 9.0 compiler will not infer that Name is not-null when it knows IsValid is true:

public IActionResult CrossTheBridge( BridgeFormModel form )
{
    if( form.IsValid )
    {
        if( form.FaveColour.Equals( "Blue" ) ) // Warning: `form.FaveColour` may be null here
        {
            Console.WriteLine( "Right, off you go" )
        }
    }
}

So we have to either assert form.FaveColour! - or do this instead:

public IActionResult CrossTheBridge( BridgeFormModel form )
{
    if( form.IsValid && form.FaveColour != null && form.Name != null && form.Quest != null )
    {
        if( form.FaveColour.Equals( "Blue" ) )
        {
            Console.WriteLine( "Right, off you go" )
        }
    }
}

We have the [NullWhen] and [NotNullWhen] attributes, but those only apply to method parameters, not other properties on the same object instance.

This wouldn't be an issue if C# still supported code-contracts, but alas, here we are... is there any way to inform the C# compiler of nullability (and other state-invariants?) based on a property?

I'd like to be able to do something like this:

public class BridgeFormModel
{
    [NotNullWhenPropertyIsTrue( nameof(IsValid) )]
    [Required]
    [Display( Name = "What is your name?" )]
    public String? Name { get; set; }

    [NotNullWhenPropertyIsTrue( nameof(IsValid) )]
    [Required]
    [Display( Name = "What is your quest?" )]
    public String? Quest { get; set; }

    [NotNullWhenPropertyIsTrue( nameof(IsValid) )]
    [Required]
    [Display( Name = "What is your favourite colour?" )]
    public String? FaveColour { get; set; }

    [BindNever]
    public Boolean IsValid =>
        !String.IsNullOrWhiteSpace( this.Name ) && 
        !String.IsNullOrWhiteSpace( this.Quest ) && 
        !String.IsNullOrWhiteSpace( this.FaveColour );
}

As NotNullWhenPropertyIsTrue is not a real attribute, I'm wondering if there's some way to write a Roslyn extension or analyser that can implement the necessary logic - or provide null-safety assertions to Roslyn.

1 Answers

You may look at MemberNotNullWhen attribute, which was introduced in C# 9 and .NET 5 and write something like this:

[MemberNotNullWhen(true, nameof(Name), nameof(Quest), nameof(FaveColour))]
public Boolean IsValid =>
        !String.IsNullOrWhiteSpace( this.Name ) && 
        !String.IsNullOrWhiteSpace( this.Quest ) && 
        !String.IsNullOrWhiteSpace( this.FaveColour );

Design notes and additional details can be found in the dotnet runtime issue #31877

Related