jstuardo
jstuardo

Reputation: 4405

Wondering why to use `string?` instead of `string` in a property declaration

I have always developed using ASP.NET Framework, where I used a string property this way: public string FirstName { get; set; }.

Now I started a new .NET Core 6 project and I declared the same property in a custom IdentityUser class.

In this case, Visual Studio told me that it is better to use nullable string. Why does it suggest that since a string type can be already null?

enter image description here

Suggestion message appears in Spanish but it is basically what I have described.

That suggestion is gone when I use string?. Note that if I use Nullable<string> shows a compiler error since string is a reference type.

Just wondering.

Thanks Jaime

Upvotes: 1

Views: 1805

Answers (1)

Joe
Joe

Reputation: 6816

No need to use Nullable<> type. That's for value types. Just decide if you want to use the Nullable Context. You will find this setting under

Project Properties >> Build >> General >> Nullable.

enter image description here

You have two basic options here.

  1. Turn off the setting to make your code work like before (I would not do this)
  2. Make your code honor the setting.

When this Nullable Context enabled, you are telling the compiler the following:

  • Any reference type declared without the ? symbol may never be null. It must always refer to a valid object.
  • Likewise any reference type declared with the ? symbol right after the type means that it may be null, just like old-school C# code.

That goes for strings, or any other reference type. The code-analyzer to checks for this and complains. So when you declare your property like this...

public string FirstName { get; set; }

Then you need to ensure in your constructor that this property is initialized to some valid string object or the code-analyzer will complain. Or you could declare it with a default value like this:

public string FirstName { get; set; } = string.Empty

If you want to be able to set the FirstName to null -- if that actually makes sense for your project -- then declare it with the question mark

public string? FirstName { get; set; }

Now it acts like an old style reference type, before C# 8.0.

Once you turn this setting on you'll find yourself dealing with this a lot. If you have warnings set to the highest level you'll be forced to chase down all these things in your code and address them. This is a good thing. Don't avoid it. Yes it is a pain to address up front but it saves you countless headaches down the road.

I once did it for an established application and spent two full days fixing all the warnings it generated. But in the year or two since then, I have lost count of the number of times this feature made the compiler/code-analyzer catch me failing to initialize a non-nullable reference type and saved me from a potential NullReferenceException down the line. I think this feature is priceless.

Nullable Context forces you to think about every reference type you have when you write the API. Can that reference be null or not? It makes you set the rule and honor it and is a lifesaver in a large project with multiple developers

Note: If you do use Nullable Context, you will want to be familiar with a very useful code attribute to use on some out values for functions: The [MayBeNullWhen] attribute. It comes in handy when you write a Try-type function to retrieve a reference type.

For example, suppose you wrote a function like this. This would work fine before but generates errors with Nullable Context enabled

public bool TryGetWidget(out Widget value)
{
    value = null;       // *** ERROR: Not valid if value is not nullable

    if (/* some code that tries to retrieve the widget */) 
        value = retrievedWidget;

    return value != null
}

The implication here is that the function might not succeed and return false. But if Widget is a reference type, then it will have to set the out value to be null in this case. Nullable Context will not allow that. You declared the out value as out Widget not out Widget? So you cannot set it to null. So what do you do?

You could change the argument to out Widget?

public bool TryGetWidget(out Widget? value)

That would make the function build. But then the Nullable context would complain if you tried to use the return value after the function returned true.

if (TryGetWidget(out var widget))
    widget.SomeFunction();   // ERROR: `widget` might be null

Seems you can't win either way, doesn't it? Unless you use [MayBeNullWhen(false)] on the function declaration

public bool TryGetWidget([MaybeNullWhen(false)] out Widget value)

Now, your function will compile and your code that calls it will compile. The compiler/code-analyzer is smart enough to realize that a true return means you can use the out reference and a false returns means you cannot.

In addition to the ? operator there is also the ! operator. This one tells the compiler, "Ignore nullability. Assume this reference is valid" So if you had a function like this

Widget? GetWidget();

The fillowing code would not compile

GetWidget().SomeFunction(); // ERROR: Return value of GetWidget() Might be null

but this would compile by forcing the compiler to pretend the return value is valid.

GetWidget()!.SomeFunction(); // Better hope that returned reference is valid.

Upvotes: 4

Related Questions