So I use C# and I often see many devs use null.
What and which kind of situation do you use this variable?
I am reading c# guide on programming book and I am on Clearing memory now and I haven't encountered null yet. Should I be worried?
Videos
Null: The Billion Dollar Mistake. Tony Hoare:
I call it my billion-dollar mistake. It was the invention of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object oriented language (ALGOL W). My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn't resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years. In recent years, a number of program analysers like PREfix and PREfast in Microsoft have been used to check references, and give warnings if there is a risk they may be non-null. More recent programming languages like Spec# have introduced declarations for non-null references. This is the solution, which I rejected in 1965.
null is a sentinel value that is not an integer, not a string, not a boolean - not anything really, except something to hold and be a "not there" value. Don't treat it as or expect it to be a 0, or an empty string or an empty list. Those are all valid values and can be geniunely valid values in many circumstances - the idea of a null instead means there is no value there.
Perhaps it's a little bit like a function throwing an exception instead of returning a value. Except instead of manufacturing and returning an ordinary value with a special meaning, it returns a special value that already has a special meaning. If a language expects you to work with null, then you can't really ignore it.
TL;DR
The key to understanding what null! means is understanding the ! operator. You may have used it before as the "not" operator. However, since C# 8.0 and its new "nullable-reference-types" feature, the operator got a second meaning. It can be used on a type to control Nullability, it is then called the "Null Forgiving Operator".
Basically, null! applies the ! operator to the value null. This overrides the nullability of the value null to non-nullable, telling the compiler that null is a "non-null" type.
Typical usage
Assuming this definition:
class Person
{
// Not every person has a middle name. We express "no middle name" as "null"
public string? MiddleName;
}
The usage would be:
void LogPerson(Person person)
{
Console.WriteLine(person.MiddleName.Length); // WARNING: may be null
Console.WriteLine(person.MiddleName!.Length); // No warning
}
This operator basically turns off the compiler null checks for this usage.
Technical Explanation
The groundwork that you will need to understand what null! means.
Null Safety
C# 8.0 tries to help you manage your null-values. Instead of allowing you to assign null to everything by default, they have flipped things around and now require you to explicitly mark everything you want to be able to hold a null value.
This is a super useful feature, it allows you to avoid NullReferenceExceptions by forcing you to make a decision and enforcing it.
How it works
There are 2 states a variable can be in - when talking about null-safety.
- Nullable - Can be null.
- Non-Nullable - Cannot be null.
Since C# 8.0 all reference types are non-nullable by default. Value types have been non-nullable since C# 2.0!
The "nullability" can be modified by 2 new (type-level) operators:
!= fromNullabletoNon-Nullable?= fromNon-NullabletoNullable
These operators are counterparts to one another. The Compiler uses the information that you define with these operators to ensure null-safety.
Examples
? Operator usage.
This operator tells the compiler that a variable can hold a null value. It is used when defining variables.
Nullable
string? x;xis a reference type - So by default non-nullable.- We apply the
?operator - which makes it nullable. x = nullWorks fine.
Non-Nullable
string y;yis a reference type - So by default non-nullable.y = nullGenerates a warning since you assign a null value to something that is not supposed to be null.
Nice to know: Using object? is basically just syntactic sugar for System.Nullable<object>
! Operator usage.
This operator tells the compiler that something that could be null, is safe to be accessed. You express the intent to "not care" about null safety in this instance. It is used when accessing variables.
string x;
string? y;
x = y- Illegal!
Warning: "y" may be null - The left side of the assignment is non-nullable but the right side is nullable.
- So it does not work, since it is semantically incorrect
- Illegal!
x = y!- Legal!
yis a reference type with the?type modifier applied so it is nullable if not proven otherwise.- We apply
!toywhich overrides its nullability settings to make it non-nullable - The right and left side of the assignment are non-nullable. Which is semantically correct.
WARNING The
!operator only turns off the compiler-checks at a type-system level - At runtime, the value may still be null.
Use carefully!
You should try to avoid using the Null-Forgiving-Operator, usage may be the symptom of a design flaw in your system since it negates the effects of null-safety you get guaranteed by the compiler.
Reasoning
Using the ! operator will create very hard to find bugs. If you have a property that is marked non-nullable, you will assume you can use it safely. But at runtime, you suddenly run into a NullReferenceException and scratch your head. Since a value actually became null after bypassing the compiler-checks with !.
Why does this operator exist then?
There are valid use-cases (outlined in detail below) where usage is appropriate. However, in 99% of the cases, you are better off with an alternative solution. Please do not slap dozens of !'s in your code, just to silence the warnings.
- In some (edge) cases, the compiler is not able to detect that a nullable value is actually non-nullable.
- Easier legacy code-base migration.
- In some cases, you just don't care if something becomes null.
- When working with Unit-tests you may want to check the behavior of code when a
nullcomes through.
Ok!? But what does null! mean?
It tells the compiler that null is not a nullable value. Sounds weird, doesn't it?
It is the same as y! from the example above. It only looks weird since you apply the operator to the null literal. But the concept is the same. In this case, the null literal is the same as any other expression/type/value/variable.
The null literal type is the only type that is nullable by default! But as we learned, the nullability of any type can be overridden with ! to non-nullable.
The type system does not care about the actual/runtime value of a variable. Only its compile-time type and in your example the variable you want to assign to LastName (null!) is non-nullable, which is valid as far as the type-system is concerned.
Consider this (invalid) piece of code.
object? null;
LastName = null!;
null! is used to assign null to non-nullable variables, which is a way of promising that the variable won't be null when it is actually used.
I'd use null! in a Visual Studio extension, where properties are initialized by MEF via reflection:
[Import] // Set by MEF
VSImports vs = null!;
[Import] // Set by MEF
IClassificationTypeRegistryService classificationRegistry = null!;
(I hate how variables magically get values in this system, but it is what it is.)
I also use it in unit tests to mark variables initialized by a setup method:
public class MyUnitTests
{
IDatabaseRepository _repo = null!;
[OneTimeSetUp]
public void PrepareTestDatabase()
{
...
_repo = ...
...
}
}
If you don't use null! in such cases, you'll have to use an exclamation mark every single time you read the variable, which would be a hassle without benefit.
Note: cases where null! is a good idea are fairly rare. I treat it as somewhat of a last resort.
String str = null;
means a String reference, named str, not pointing to anything
String str = "";
means a String reference, named str, pointing to an actual String instance. And for that String instance, it is a zero-length String, but it is still an actual object.
Just a little update with some diagram which hopefully can help you visualize that:
assume I have
String nullStr = null;
String emptyStr = "";
String myStr = "ab";
What it conceptually is something look like:
// String nullStr = null;
nullStr ----------> X pointing to nothing
// String emptyStr = "";
+------------------+
emptyStr ---------> | String |
+------------------+
| length = 0 |
| content = [] |
+------------------+
// String myStr = "ab";
+------------------+
myStr ------------> | String |
+------------------+
| length = 2 |
| content = [ab] |
+------------------+
(of course the internal structure of the String object is not the real thing in Java, it is just for giving you an idea)
More edit for the rationale behind NULL:
In fact in some language they do not provide concept of NULL. Anyway, in Java (or similar language), Null means semantically different from "empty" object. Use String as an example, I may have a People class with a String preferedTitle attribute. A Null preferedTitle means there is NO preferred title for that people (so that we need to derive and show the title for it, maybe), while a preferedTitle being an empty string means there IS a preferred title, and that's showing nothing.
Btw, although a bit off topic: concept of Null is seen as problematic for some people (because all those extra handling it need etc). Hence some languages (e.g. Haskell) are using some other ways to handle the situation where we used to use Null.
String str is a reference to an object. That is, it's not an actual object, but a variable which can contain the address of an object. When you assign a value to str you are changing the address stored within and changing which object it addresses.
null is reference value which points to no object. It's about as close to nothing as you can get. If you assign null to a String reference (String str = null;), you cannot then invoke any method of String using that reference -- all attempts will result in NullPointerException.
"" is a character String which contains no characters -- zero length. It is still an object, though, and if you assign its address to your String reference variable (String str = "";) you can then take its length, compare it to another String, extract its hashCode, etc.