a history of advancements in the software design and implementation of the language's safety.
Manipulating null references, a programming concept that many developers find tedious, is one of these improvements.
Null references can lead to a variety of problems in your code, such as information gaps and exceptions.
You will learn how to deal with null references in the most recent C# and.NET programming language version from this article. Let no null pass go unattended is the name of the game.
There will be multiple stages to this demonstration, each with a brief demo. Please use the table of contents below to navigate around.
Prerequisites
How to Use Nullable Reference Types
Person? left = null;
Person? bob = new Person("Bob", "Coder");
Person fowler = new Celebrity("Martin", "Fowler", "famous books");
Person martin = new Celebrity("Bob", "Martin", "SOLID principles");
As you can see, bob, the second reference, is declared nullable even though he is assigned to a proper object. In situations where an object is approaching from the outside and you are unsure if it will be present or not, that is acceptable.
It is important to ensure that a non-nullable reference is not assigned a nullable one. That will result in a compile-time warning, which you can choose to escalate to a compile-time error.
It is crucial to realize that nullability is an indication provided to the compiler rather than a feature of the type. Nullable reference types are never stored in the compiled type itself; they are only used in the compile-time analysis.
The twist comes in that, as shown in the code below, we can freely mark any reference to the generic parameter type as nullable. Just like any other reference, this one is open to final assignment analysis.
void Showcase<T>(string caption, Action<T?> action, params T?[] objects)
{
Console.WriteLine($"Showcasing {caption}:".ToUpper());
foreach (T obj in objects) action(obj);
Console.WriteLine();
}
For the remainder of this piece, we have defined the utility function to illustrate every scenario that includes null references. Declaring this generic function as Showcase<T?> would be a compile-time error, as I have already mentioned, even though it would be perfectly acceptable to accept a nullable T? in the argument list. causes your head to spin!
What comes next is even more puzzling: why not take nullable out of the argument list? What would that entail?
That would leave the decision of whether or not to be nullable to the caller because, listen carefully now, a concrete generic parameter type can be nullable. During compilation, it ascertains the nullability of references, which is a real thing.
I hope that by now you are beginning to understand these ideas more. I would definitely encourage you to study more about nullability of types, but it would take up a lot of space to go into detail on this topic. It is a permanent feature of C# now.
While the second call above requires non-nullable references, the first call above permits null references in the arguments. In that scenario, the compiler would examine the references supplied as arguments and issue a warning if any of them were or might be null.
Our quick tutorial on C# nullable reference types is now complete. We are prepared to move forward with more complex issues.
Pay attention to the empty line in the output. That is where we have passed null to the Console.WriteLine. The WriteLine method accepts null and treats it the same way it treats an empty string.
How to Use the is null and is not null Patterns
An object is being tested by the is operator against a pattern. This operator will come up again and again in the next sections.
Its most basic application—testing against the null pattern—is demonstrated in this demo. There are two possibilities with meanings that seem to be self-explanatory: is null and is not null. Yes, but that would be a grave error!
Is null and is not null patterns cover corner cases, which may have been the primary motivation behind their initial introduction. Calling any overload of the == and!= operators will be avoided by both patterns.
How to Use Type-Test-and-Set Patterns
- To test whether a reference references an actual object, that is the person is not null pattern.
- To add the test whether that object is assignable to a particular type, we use the type pattern instead: person is Celebrity.
- Finally, to capture the reference to the desired type and use it in subsequent statements and expressions, we use the full-blown type-test-and-set expression: person is Celebrity celeb.
These are the three steps—each more effective than the last—of information extraction from a reference.
Here is the method that exercises the most detailed form, all contained in one condensed expression: testing against null and downcasting.
It's possible that you've noticed how well these expressions use safe downcasting. For many years, downcasting was discouraged because it was believed—mostly correctly—to be the source of design and code flaws.
However, things are changing! Functional programming is where type tests and set expressions originate in software development.
The differences between type testing and downcasting as they were implemented in older object-oriented languages are not appropriate topics for this article to cover. Before passing judgment, I implore you to educate yourself further on this fascinating subject.
How to Use Property Patterns
If you are not interested in downcasting, you do not need to specify the type. It will be the kind of reference that is located to the operator's left.
However, employing the is operator suggests a null test. On the right side of the expression, any reference that passes the is test will be non-null and safe to check its property values.
As a result, we interpret the condition of this instruction as follows: In the event that person is not null and Bob is the value of its FirstName property, then...
How to Use the Null Propagation and Null Coalescing Operators
The difference between calling ToString on Person and on Person? types is significant. Since the latter is nullable, a careless call could result in the dereferencing of a null reference and, as you can imagine, a NullReferenceException.
Person a = ...;
Person? b = ...;
string x = a.ToString(); // safe
string y = b.ToString(); // unsafe
Enter the null-propagation operator (?.)! We can safely make an optional call to a method, provided the reference is non-null.
Person a = ...;
Person? b = ...;
string x = a.ToString(); // safe
string? y = b?.ToString(); // safe
However, note the results. The call on a null reference will be ignored if the method returns void. The nullable version of the type will be the outcome if the method returns a type. You understand that you cannot expect a string from ToString on a nullable reference? Instead, all the compiler can guarantee is a nullable string.
What if we truly desired a string, an authentic one? The null-coalescing operator (??) is entered! By giving a default value to use in the event that the actual value is null at run time, we can quickly transform a nullable reference into a non-nullable one.
How to Work With Optional Objects
In actuality, nulls won't be used in the final strategy for handling nulls in this article. One more puzzle! By representing the objects as potentially missing, the goal is to completely avoid nulls. Keep in mind that the word "possibly" will be included in the type declaration, just like nullability was.
This brief explanation won't even come close to teaching you everything there is to know about optional objects if you're new to them. Optional objects are not natively supported in C#. One of the many implementations that NuGet offers is the LanguageExt library, which is the most widely used.
An object of any kind that is either present or absent is called an optional object. The optional object itself will always exist, regardless of the circumstances. You have another puzzle to solve!
Here is how we would declare a few optional objects:
using LanguageExt;
Option<Person>[] maybePeople =
{
Option<Person>.None,
Option<Person>.Some(bob),
Option<Person>.Some(fowler),
Option<Person>.Some(martin),
};
Typically, the two forms of an optional object are called None and Some. An actual object must be present in the Some variant. The code that will never have a null reference and optional object creation are now complete.
However, how does this differ from nullable references? Why even should we utilize optional objects?
The short story is that, if the optional object has content, we can apply functions to it. If there is no content, the optional object will either call the function without passing any data to it or invoke it and pass the content to it.
The Match method handles both scenarios: in the event that the person is absent, it either replaces the missing person with an empty string or maps the Person object to a string. If content is present, the Do method will only pass it to the console.
Here is the printout produced by the Do method:
SHOWCASING OPTIONAL OBJECTS:
Bob Coder
Martin Fowler known for famous books
Bob Martin known for SOLID principles
Printing only the Some variants is what you will see. Because the optional instance has disregarded the action passed to its Do method, the lone missing object in the input array has not generated any output.
The ability of optional objects to apply other functions makes them a superior choice over nullable references. It's possible that our codebase already contains a wide variety of classes and methods, all of which rely on non-nullable references. Using optional objects can help close the gap between common methods that only work when nothing is missing and possibly missing objects.