Skip to content
Theory · Value and reference types · lesson 8 of 11

int? and string?

An int variable always holds a number. But sometimes there is no honest number: a user did not give their age, a sensor did not send the temperature. Zero would lie here: an age of 0 is an age too.

You cannot put null into an int. A value type holds the value itself, not a reference, and the line int age = null; does not compile: the compiler answers with error CS0037. The same goes for your struct: the line Point p = null; does not compile.

Picture a form where the "Age" field has a "filled in" checkbox next to it. For this, C# has types with a question mark: int? holds a number and a mark saying whether it is there.

The int? type

A nullable type is a value type with a ? sign: int?, double?, bool?. The full name of int? is Nullable<int>. It is a struct of two parts: the value itself and a flag saying whether it is there.

C#
int? age = null;
Console.WriteLine(age.HasValue);  // False
Console.WriteLine(age ?? -1);     // -1
age = 30;
Console.WriteLine(age.HasValue);  // True
Console.WriteLine(age.Value + 1); // 31

Let's go through the example line by line:

  1. int? age = null;—the field is empty, the box is not ticked.
  2. age.HasValue prints False: there is no value.
  3. age ?? -1 supplies -1. The ?? operator works with int? the same way as with references.
  4. After age = 30 there is a value, and HasValue prints True.
  5. age.Value reads the number, so age.Value + 1 prints 31.

Here the form and int? part ways. On paper, you can write in a number and forget to tick the box. In int?, the box ticks itself: assign a number, and HasValue becomes true.

int? and int are different types. The line int x = age; does not compile (error CS0266): age may turn out to hold no number. You take the number out explicitly: with age ?? 0, or with age.Value once you have checked HasValue.

?. gives an int?

A string's property Length has the type int. But if there is no string, there is no length either, so word?.Length gives an int?:

C#
string? word = null;
int? length = word?.Length;
Console.WriteLine(length.HasValue); // False
int safe = word?.Length ?? 0;
Console.WriteLine(safe); // 0

With null in word, word?.Length gives an empty int?, and HasValue prints False. To get a plain int, you add ?? 0: then 0 replaces the missing value.

Reference types and string?

A reference variable can always hold null: that is how references work. So for a reference type, the ? sign means something else—it is a hint for the compiler:

  • string—null is not expected here;
  • string?—null may end up here.

New projects and programs run with dotnet run hello.cs have a check called nullable reference types turned on. The compiler uses these hints to track where null may get to, and it warns about the dangerous spots:

C#
string? nick = null;
string name = nick;             // ✗
Console.WriteLine(nick.Length); // ✗
TEXT
warning CS8600: Converting null literal or possible null value to non-nullable type.
warning CS8602: Dereference of a possibly null reference.

What the two warnings mean:

  • CS8600—a possible null goes into a variable of a type without ?;
  • CS8602—a member is accessed through a variable that may hold null.

These are warnings, not errors: the program compiles and runs. But the third line stops it at once with a NullReferenceException, the one CS8602 warned about. The warning goes away when the compiler sees a check: inside if (nick != null), you may access nick.Length.

int? or string?

Interviewers like to ask: how does int? differ from string?? The answer has two parts:

  • int? is a separate type, Nullable<int>: a struct with a value and a HasValue flag that exists while the program runs too;
  • string? is the same type string with a note for the compiler: at run time there is no difference between string and string?, and only the compiler warns about null.

Common mistake

Someone read Value without checking whether there is a value:

C#
int? score = null;
Console.WriteLine(score.Value); // ✗
TEXT
Unhandled exception. System.InvalidOperationException: Nullable object must have a value.

The compiler warns about this line (CS8629: Nullable value type may be null.), but it compiles the program. When it runs, Value on an empty int? stops it with an InvalidOperationException. Check HasValue before Value, or take score ?? 0.

Key points

  • int? is a value type that can hold null: HasValue says whether there is a value, and Value reads it.
  • Value on an empty int? throws an InvalidOperationException; reading through ?? is safe.
  • string? is a note for the compiler: it warns with CS8600 and CS8602, but the program compiles.
  • int? is a separate type, Nullable<int>, while string and string? are the same at run time.