23

Consider the following code:

#nullable enable
class Foo
{
    public string? Name { get; set; }
    public bool HasName => Name != null;
    public void NameToUpperCase()
    {
        if (HasName)
        {
            Name = Name.ToUpper();
        }
    }
}

On the Name=Name.ToUpper() I get a warning that Name is a possible null reference, which is clearly incorrect. I can cure this warning by inlining HasName so the condition is if (Name != null).

Is there any way I can instruct the compiler that a true response from HasName implies a non-nullability constraint on Name?

This is important because HasName might actually test a lot more things, and I might want to use it in several places, or it might be a public part of the API surface. There are many reasons to want to factor the null check into it's own method, but doing so seems to break the nullable reference checker.

John Melville
  • 3,613
  • 28
  • 30
  • 1
    IMO you should use `HasValue` on a nullable type, not check it against `null`. It probably doesn't affect your problem though. – fredrik Nov 24 '19 at 14:45
  • I think for you case, you can wrap you code with `#nullable disable` then `#nullable enable` or `restore` again afterwards (https://learn.microsoft.com/en-us/dotnet/csharp/nullable-references#nullable-contexts). – GaryNg Nov 24 '19 at 15:10
  • 6
    you could use the "dammit" `!` operator. `if(HasName) { Name = Name!.ToUpper(); }` – Jan Paolo Go Nov 24 '19 at 21:39
  • 1
    for a multi-thread application, you could have Name being null after the HasName check, using the variable locally instead of going back to the property (who knows what the property might do in its getter) is going to give some funky bugs (remember the using of an event handler where this has happened alot) – XIU Nov 28 '19 at 09:36

3 Answers3

13

UPDATE:

C# 9.0 introduced what you're looking for in the form of MemberNotNullWhenAttribute. In your case you want:

#nullable enable
class Foo
{
    public string? Name { get; set; }

    [MemberNotNullWhen(true, nameof(Name))]
    public bool HasName => Name != null;
  
    public void NameToUpperCase()
    {
        if (HasName)
        {
            Name = Name.ToUpper();
        }
    }
}

There's also MemberNotNullAttribute for unconditional assertions.

Old answer:

I looked around at the different attributes from System.Diagnostics.CodeAnalysis and I couldn't find anything applicable, which is very disappointing. The closest you can get to what you want appears to be:

public bool TryGetName([NotNullWhen(true)] out string? name)
{
    name = Name;
    return name != null;
}

public void NameToUpperCase()
{
    if (TryGetName(out var name))
    {
        Name = name.ToUpper();
    }
}

It looks pretty cumbersome, I know. You can look at the MSDN docs for nullable attributes, maybe you'll find something neater.

V0ldek
  • 9,623
  • 1
  • 26
  • 57
  • 2
    Seems like we need more attributes or something like typescript's assertions – Stilgar Nov 24 '19 at 23:31
  • I'll pick this one as the answer, because it appears that the real answer, as I feared, is "no, c# doesn't do that yet." – John Melville Nov 28 '19 at 04:49
  • @JohnMelville I wasn't able to find a proposal for such a feature either, so I don't think we can expect this changing anytime soon. – V0ldek Nov 28 '19 at 08:47
  • this is the only acceptable solution in a multi-threaded system, Name could be null between the check and the usage – XIU Nov 28 '19 at 09:34
  • 2
    @XIU The compiler is already lax in this aspect. If you do `if(Name != null) return Null.ToUpper()`, there will be no warning for a null dereference, even though technically it's a TOCTOU race condition. I remember Mads Torgersen speaking about how they considered that, but it would generate so many false positives the entire nullable reference types feature would be effectively useless - 99% of the time your properties won't be changed by another thread. So all you'd need to do is make an attribute that would make the check on this property be treated as a check for null on another property. – V0ldek Nov 28 '19 at 09:41
  • Found the article: https://devblogs.microsoft.com/dotnet/nullable-reference-types-in-csharp/ under "Avoiding dereferencing of nulls" – V0ldek Nov 28 '19 at 09:47
  • 3
    I fixed the "can't find a proposal for this" problem. (https://github.com/dotnet/csharplang/issues/2997) Wish me luck. – John Melville Nov 29 '19 at 02:28
  • @JohnMelville I've seen your proposal get closed today with the info that this feature was implemented in C#9. Updated the answer. – V0ldek Apr 19 '21 at 23:51
  • Still doesn't really help when you have assertions that internally check & throw on nulls, and you expect proceeding code to no longer null check that field/property. Similar to how it works in TS. – Douglas Gaskell Dec 05 '22 at 00:13
  • @DouglasGaskell That should be doable with `MemberNotNull`, can you elaborate on your issue? – V0ldek Dec 05 '22 at 13:57
1

In C# 9.0 check out [MemberNotNull(nameof(Property))] and [MemberNotNullWhen(true, nameof(Property))] attributes.

https://github.com/dotnet/runtime/issues/31877

John Melville
  • 3,613
  • 28
  • 30
-10

String is a reference type, and nullable (e.g. int?) is nullable value types. So you can't really do this string? myString; What you need is this:

class Foo
{
    public string Name { get; set; }
    public bool HasName => !String.IsNullOrEmpty(Name);  ////assume you want empty to be treated same way as null
    public void NameToUpperCase()
    {
        if (HasName)
        {
            Name = Name.ToUpper();
        }
    }
}
daxu
  • 3,514
  • 5
  • 38
  • 76
  • You are out of date https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/nullable-value-types – Aluan Haddad Nov 24 '19 at 14:48
  • @AluanHaddad I believe you meant to send this instead https://learn.microsoft.com/en-us/dotnet/csharp/nullable-references – QmlnR2F5 Jun 05 '21 at 15:41