Nullable types gives the ability to the compiler to check for possible null return values or not assigned members to detect possibility of any NullPointerException which may occur at runtime.
Also with the usage of nullable syntax, it is easier to visually see the intention of the code about the certain issue,
To enable nullable in projects make sure you add the following configuration to your .csproj file.
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>We follow the guidelines below when writing code to properly use nullable syntax.
public class PersonService(IQueryContext<Person>? queryContext)
{
...
}Note
The DI container will resolve every dependency before initializing the object and an exception will be thrown for a if a component is not registered. Therefore, there is no possibility of dependency to be null.
The usage of ! operator negates the null check that the compiler performs, so the whole intention of enabling nullable check will be compromised and it will be very hard to spot possible bugs which may occur at runtime.
! operator should only be used to dismiss the compiler errors when we set default values for non nullable members which are;
- Assigned with dependency injection
- Assigned in the builder method
public class Person(IEntityContext<Person> _context)
{
protected Person() : this(default!) { }
...
}Note
Because of NHibernate, entities need a protected parameterless constructor and compiler will highlight an error stating that the value of _context is not assigned when leaving a constructor. Therefore we need to assign a default value to _context to dismiss the compiler error.
When using nullable parameters, the main challenge is to manage what happens within the scope of the method if a parameter has not been assigned with any value.
We should always make sure the functionality is not broken and cause a wrong intention wether a parameter is necessary or not.
public class Person
{
public string Name { get; } = default!;
...
// name is made not nullable to give responsibility to caller
public virtual Person With(string name)
{
Name = name;
}
}public record AddPerson(string? Name);
public class PersonService : IPersonService
{
...
public void AddPerson(string name) => _newPerson().With(name);
void IPersonService.AddPerson(AddPerson data)
{
if(data.Name is null) throw new ArgumentNullException();
AddPerson(data.Name);
}
}public class PersonService
{
...
public void AddPerson(string? name)
{
name ??= "John Doe";
_newPerson().With(name);
}
}public class PersonService
{
...
public void AddPerson(
string? name = default
)
{
name ??= "John Doe";
_newPerson().With(name);
}
}public class Person
{
...
public virtual Person With(string name, string? middleName)
{
...
}
}
public class PersonService
{
public void AddPerson(
string? name = default,
string? middleName = default
)
{
name ??= "John Doe";
_newPerson().With(name, middleName);
}
}public class PersonService
{
public void UpdatePerson(string? middleName = default)
{
if(middleName is null) throw new ArgumentNullException();
}
}public class PersonService
{
public void UpdateEntity(
Person person,
string? middleName = default
)
{
middleName ??= "Mike";
person.ChangeMiddleName(middleName)
}
}public class PersonService{
...
public void DeletePerson(int id)
{
_queryContext.SingleById(id)?.Delete();
}
// Even the relevant entity may not exits, the result
// code will be 200 and it will cause a miss direction
// Throw an exception instead
}public class Person{
...
Child? Child { get; set; }
...
public void Delete()
{
Child?.Delete();
...
}
}Nullable Reference Types are different than Value Types and they only instruct the compiler about members or parameters that they can be null. Therefore
string?is not aNullable<string>int?is aNullable<int>
and Nullable.GetUnderlyingType(T) for reference types are null.
public class Person
{
public string Name { get; } = default!;
public string? MiddleName { get; }
public int? Age { get; }
}
...
Type middleNamePropertyType = typeof(Person).GetProperty("MiddleName").PropertyType;
Console.WriteLine(middleNamePropertyType); //System.String
Console.WriteLine(Nullable.GetUnderlyingType(middleNamePropertyType)); //
Type agePropertyType = typeof(Person).GetProperty("Age").PropertyType;
Console.WriteLine(agePropertyType); // System.Nullable`1[System.Int32]
Console.WriteLine(Nullable.GetUnderlyingType(agePropertyType)); // System.Int32When used for comparing nullables, the operator returns false if one side of the comparison is null.
See official Microsoft Learn documentation for further details.
public class Person
{
public string? InitialName =>
Name.Length > MiddleName?.Length ? Name : MiddleName;
}
#main
var person = new Person(name: "John");
Console.WriteLine(person.FullName()) //
// Prints empty string because MiddleName is null and
// comparison result is false