In the previous article, we have discussed basic concepts about what nullable reference types means and how this feature can be enabled.
As you may already know, this feature is by default enabled on the new projects created using .NET 6. That’s why if you have an existing codebase and if you want to upgrade it to .NET 6, it may be a good idea to enable this feature now on the existing projects.
When you enable this new feature on existing codebase, the compiler may start emitting a lot of warnings. This article is a conceptual guide, that will help understanding what should be the approach in fixing those warnings.
This article is kind of conceptual guide for enabling the nullable reference types in an existing codebase and resolving the nullable warnings. So, the only pre-requisite is, the codebase should already have been using C# 8 or later version – because nullable reference types was not supported on previous versions of C#.
Overview – Fixing Nullable Warnings
As stated in the previous post, this feature, nullable reference types, can be enabled either in the source code files by using #nullable or by enabling appropriate settings in the project files. Once this feature is enabled, compiler will start performing some code analysis.
Based on this analysis, the compiler will emit the warnings wherever the code has used constructs which may lead to
NullReferenceException. Resolving those warnings is really tricky. Some warnings may seem easy to resolve, but they may generate new warnings.
We can address all the warnings by using one of the four techniques:
- Adding appropriate null checks – e.g. either an exception can be thrown or some default values can be initialized.
- Adding one of the two nullable annotations:
- ‘?’ posfixed to the type name at declaration, to make the variable nullable
- ‘!’ posfixed to the variables so as to convey compiler that the value would not be null
- Adding attributes that describe the null semantics
- Initializing the variables correctly
Now, let’s have a look at some common nullable warnings. We will discuss what that warning is about and how to resolve the warning.
Warning: Possible NULL Reference Return
This warning suggests that the return type of method is non-nullable, while the method is returning null. You will get this warning at almost all the places where method is returning null.
This error is because the reference type was nullable by default, but because enabling nullable reference types, the type is non-nullable now.
In order to fix this warning, the return type should be marked as nullable.
Warning: Possible NULL Assignment to Non-Nullable Type Variable
After we enable the nullable reference types feature, all the reference types by default become non-nullable. Only one exception – the variables declared with var keyword are considered to be nullable.
GetMessage method shown in the above snapshot, before enabling the null reference types (NRT), the code was working perfectly fine. But after enabling the NRT, the method can return null, that’s why we had to make the return type of the method to be nullable string, by appending the ‘?’ to the return type.
It resolves the nullable reference type warning as explained in previous section. But also, it introduces new warning – the result of method call now can be nullable, but the data type of the variable suggests that the data type of message variable is not null.
There are three alternatives to fix this warning:
- Change the left side of assignment and make the type nullable. This action may introduce new warnings
- Provide null check before assignment
- Annotate the API on right hand side to convey that return type is not null
- Add null-forgiving operator (‘!’) to the right hand side of assignment
All these 3 fixes are shown in the code snippet given below. The fourth fix we will discuss later in some other post, when we discuss about attributes that can be placed on methods and method parameters.
Warning: Dereference of Possibly a NULL reference
Whenever we choose to make make the variable type nullable by appending the ‘?’ operator to the type name while declaring variable, this new warning may get introduced.
This warning suggests that a variable that is being accessed, may be null. There are three resolutions to this warning:
- Add a null check and execute the path only if the variable is not null
- Add a null forgiving operator
- Another option is to add appropriate null-state analysis attributes on the APIs that is causing this warning.
The code example given below provides examples of first two resolutions from the above mentioned list:
Sometimes, this warning may be a false positive. Let’s say you already checked for null in a private method, named IsValid.
There is no way for compiler to know that this IsValid method provides null check. So, for such cases, we can use attributes to decorate the parameters to convey that the parameter would not be null if certain condition is met. Or alternatively, null-forgiving operator can also be used here.
Warning: Non-Nullable Reference Not Initialized
This warning is for all the variables (of reference types, obviously) which were declared, but were not initialized. This warning is common if you have POCO classes which contain only read-write properties. It can also be applicable for fields or local variables which are not initialized.
Especially while calling
Try**** methods, generally they need an out parameter. In those cases, many times, we might have declared variable just before that call, which may not have been initialized.
In order to fix this warning, there are four alternatives:
- Add (or modify existing) constructors or field / property initializers to ensure non-nullable members are initialized
- Check if the non-nullable properties are really supposed to be non-nullable. If they are part of optional data, you can change them to nullable.
- if there are any helper methods, we can annotated those methods with appropriate attributes
- Initialize the value to
null!to indicate that the non-nullable field is getting initialized in some other parts of code.
Below code snippet shows couple of examples which may generate this warning and their fixes.
Warning: Nullability Mismatch
This warning appears only in case the class is getting derived from other class or if the class is implementing interface(s). The warning says that the signatures of the methods are not matching. The nullability of return types and/or parameters in method signature is not matching to delegates / interface definition / base class.
Warning: Code Does Not Match Attribute Declaration
As I mentioned earlier, we can apply some annotations on methods to guide the compiler’s null analysis. But if compiler figures out that the annotations are not matching with the code outcome, then this kind of warning is emitted.
There are very less chances that you would see this warning while enabling nullable reference types in an existing codebase. In fact, you will not see this warning as long as you do not use annotations on APIs (attributes on methods).
The code snippet given below shows an example of such occurrence. Here, the attribute is suggesting that out parameter is not null when returned value is true. But in the code, out parameter is getting set to null even when method returns true. That’s why compiler would produce the warning.
In this case the only resolution is to either modify the code or modify the annotations so that expectations set by annotations match with the code.
Verifications and Testing
If your solution is complex, I would suggest to go project by project. It means that we should select a project, enable nullable reference types feature and then resolve all the warnings for that project. Then select another project.
Also, while migrating to adopt this feature, it is immensely important not to apply any unrelated refactoring. I will try to share some tips and tricks in the coming post that would enable you to decide your path for this migration and it will also give you some idea about how to estimate that activity.
Nullable reference feature does not change run-time behavior. It is just a compile time feature, compiler can analyze the code and can warn developers wherever it thinks null reference exception can occur.
So, for verifications, there is no special efforts required. We can just run existing automated tests to ensure that code works as expected. As long as tests are green, you should be good to merge the code.
What should I do after all warnings are fixed / verified ?
This section is applicable only if you migrated existing codebase to adopt this new feature. The codebase may have a lot of business functionality already developed before adopting this feature, and hence, during the migrations, it may not be possible to discuss every entity in detail to decide its nullability.
So, cutting long story short, deciding on nullability is crucial here and it may be tricky to figure out appropriate nullability if you are working on complex / huge code base.
Some tips that you can use while deciding whether a property or field should be nullable or not are:
- you can check the database design if the field maps to database to understand if field should be nullable.
- You can check if field (or its corresponding fields) is marked as required in some other layers. If it is required, then mostly it should be non-nullable.
- Otherwise, we can check the related methods to see how the objects of the concerned types are getting used. Based on the usage, you should be able to infer, most probable nullability that you should assign to the property / field / variables.
- You can also discuss with team / product owner wherever you have queries to ensure that your analysis is matching the overall understanding of the solution.
So, because this activity is tricky, there are chances that you may have missed few things. Or it may also happen that you applied correct nullability but the feature requirements got changed. So, there are two things that we should keep an eye on :
- IF any design / requirement change would affect existing nullability. If it is affecting, then related fields / properties / variables should be changed appropriately.
- If any thing was missed as a part of initial migration, then when any related work is going on, team should always keep their eyes open to catch such issues and prioritize those fixes as per discussions with team.
- Enable warnings as errors for Nullable warnings (
how topart is mentioned in my previous article).
I hope you find this article helpful. Let me know your thoughts.