Skip to content

Instantly share code, notes, and snippets.

@habib-sadullaev
Last active September 10, 2023 09:14
Show Gist options
  • Save habib-sadullaev/1e4397c302aaaf83b5d5f4698bc94cd6 to your computer and use it in GitHub Desktop.
Save habib-sadullaev/1e4397c302aaaf83b5d5f4698bc94cd6 to your computer and use it in GitHub Desktop.
### Nullable Reference Types
https://devblogs.microsoft.com/dotnet/nullable-reference-types-in-csharp/
https://learn.microsoft.com/en-us/ef/core/miscellaneous/nullable-reference-types#non-nullable-properties-and-initialization
The purpose of nullable warnings is to minimize the chance that your application throws a `System.NullReferenceException` when run.
To enable Nullable Reference Types for all code in a project, you can add the following to its `.csproj` file:
```csproj
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
...
<Nullable>enable</Nullable>
<WarningsAsErrors>Nullable</WarningsAsErrors>
...
</PropertyGroup>
</Project>
```
You'll address almost all warnings using one of four techniques:
- Adding necessary null checks.
- Adding `?` or `!` nullable annotations.
- Adding [Attributes for null-state static analysis](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/attributes/nullable-analysis).
- Initializing variables correctly.
***The null-forgiving operator `!` should be used with caution and only in situations where you’re certain that a nullable expression will never evaluate to `null` at runtime. It’s used to suppress nullable warnings when the compiler can’t determine that an expression is not `null`.
This operator doesn’t change the runtime behavior of your code. It only suppresses nullable warnings at compile-time. If an expression that’s been marked with `!` evaluates to `null` at runtime, a null reference exception will still be thrown.***
### Bad
[link](https://sharplab.io/#v2:C4LgTgrgdgNAJiA1AHwAICYCMBYAUHgWQAooBTAdyIEoqBuPPVTANgAJUAWV4gBVLADOAeyisADlTwBvPKzntMATiJiAdADEAloOAA5AIYBbUqoAypKAHNgACzqtZ8psrWn9AvUZPmrtug1x5Vk0AM1YVVQJNODgAG1IDY1YAQgBeVigIWNjJQPkZPKC5ZwiomPjE7wtrO1oHQtYAXzxm/FxUAGZ2dFY+QRFpR2KupgAGVi0dStYpVktSYDqBBbrG1nTM7OT6Bs6FUYB+bmi4hK8ZuZXWZcWmofYRzHG3D2nZ+dub1fWMrNjtlpAA===)
```C#
using System;
M(new()); // can't detect that `FistName` and `LastName` are not initialized
static void M(Person p)
{
Console.WriteLine(p.FirstName.Length);
Console.WriteLine(p.LastName.Length);
if (p.MiddleName != null)
{
Console.WriteLine(p.MiddleName.Length); // ok
}
}
public class Person
{
public string FirstName { get; set; } = null!; // NullReferenceException
public string? MiddleName { get; set; }
public string LastName { get; set; } = null!;
}
```
### Good
[link](https://sharplab.io/#v2:C4LgTgrgdgNAJiA1AHwAICYCMBYAUHgWQAooBTAdyIEoACAbxoDEBLMAZ2ADkBDAW1JoBeGgCJGAe3EiYNADLcOPfkNEAhbmBE0AvlQDcNPHlSYAbDVQAWGsQAKpduKg0ADlTx08NbxcwBOIhcAOhZ2Lj5SINlSKABzYAALfUNcH18A4PlFCKiY+KSDAHpCmk4IABtygCVSADMHGIBjUgBRAA9ml2BmJy8fPu9mWppAoIJmODhy0iUBAEJhKAry91SfTzW07xMMsYmpmZzouMTk4ppxAGsUtO08O/xcVABmC3Qae0coDwGLV5MAAxMVjZZQMWKkYAGNiQgzaFRLSpzPS/F6+AEAfhs+2ms3oNAhUJoMKJDzSaMBcgU4TBBNhxPp8MWy2R9yAA===)
```C#
using System;
M(new() { FirstName = "Foo", LastName = "Bar" }); // ok
static void M(Person p)
{
Console.WriteLine(p.FirstName.Length);
Console.WriteLine(p.LastName.Length);
if (p.MiddleName != null)
{
Console.WriteLine(p.MiddleName.Length); // ok
}
}
public class Person
{
public string FirstName { get; set; } = null!;
public string? MiddleName { get; set; }
public string LastName { get; set; } = null!;
}
```
- declare property as nonnullable and use `null!` to supress the warnings.
It's a preferable way to use
- in an existing project to minimize changes.
- in a new project when there is no other options described below. e.g. types are utilized by IOptions<>, Dapper, serializators, EF (sometimes) etc.
```csharp
public class Foo
{
public Bar Bar { get; set; } = null!;
}
```
In this example, we’ve used the null-forgiving operator `!` to suppress the nullable warning for the `Bar` property. This tells the compiler that we’re aware that the `Bar` property is being assigned a `null` value and that we’re taking responsibility for any null reference exceptions that may occur as a result.
Keep in mind that this should be done with _caution_ as it can hide _potential issues_ in your code. It’s generally _better_ to ensure that non-nullable properties are _always_ assigned a non-null value.
- declare a propery as nullable if it can be `null`
```csharp
public class Foo
{
public Bar? Bar { get; set; }
}
```
This will allow you to assign a null value to the `Bar` property without receiving a nullable warning. You’ll _need_ to check if the `Bar` property is `null` before accessing its members to _avoid_ null reference exceptions.
- add a constructor with a parameter of a property type:
```csharp
public class Foo
{
public Foo(Bar bar) => Bar = bar;
public Bar Bar { get; set; }
}
```
##### Bad
[link](https://sharplab.io/#v2:C4LgTgrgdgNAJiA1AHwAICYCMBYAUHgfQAIBeIqAUwHciAxAe3oAooIAbNgQgEoBuPPKgDMRDHUZ4A3niKzRIhswBCAQzBEARmphFVAL00q93GXOm45logEsAZkSZb11gM7l2bExas/gACzB6GkoaAEEwAHMIAFsKKGAAOQ8AUQAPAGMKAAdga3ooFhVY+ltHNW4+Ux87By0DV3cOLx9fAKDyaiJwqNj4pI40zJy8gqgiihKy40rvWQBfAVn5cWU1QzBuIhAif1cysB0nADp9TaqiSSIF3GvBETFVdSlz4V0jN4NLiIpgXiIXH5/OakRpcfg3RavB5GZ43IA)
```C#
using System;
_ = new Foo(null!);
public class Foo
{
public Foo(Bar bar, Baz baz)
{
if (bar is null)
throw new ArgumentNullException(nameof(bar));
if (baz is null)
throw new ArgumentNullException(nameof(baz));
}
public Foo(Bar bar) : this(bar, bar.Baz) // not ok. NullReferenceException on bar.Baz because bar is null
{ }
}
public class Bar
{
public Baz Baz { get; set; } = null!;
}
public class Baz
{
}
```
##### Good
[link](https://sharplab.io/#v2:C4LgTgrgdgNAJiA1AHwAICYCMBYAUHgfQAIBeIqAUwHciAxAe3oAooIAbNgQgEoBuPPKgDMRDHUZ4A3niKzRIhswBCAQzBEARmphFVAL00q93GXOm45logEsAZkSZb11gM7l2bExas/gACzB6GkoaAEEwAHMIAFsKKGAAOQ8AUQAPAGMKAAdga3ooFhVY+ltHNW4+Ux87By0DV3cOLx9fAKDyaiJwqNj4pI40zJy8gqgiihKy40rvWQBfAVn5cWU1QzBuIhAif1cysB0nAH4AOn0eIgB6S6J6AGsqokkiBdxXwRExVXUpR+FdIwAgzPCIUYC8IguMEQuakRpcfhvRb/L5GX5vIA=)
```C#
using System;
_ = new Foo(null!);
public class Foo
{
public Foo(Bar bar, Baz baz)
{
if (bar is null)
throw new ArgumentNullException(nameof(bar)); // ArgumentNullException because bar is null
if (baz is null)
throw new ArgumentNullException(nameof(baz));
}
public Foo(Bar bar) : this(bar, bar?.Baz!) // ok
{ }
}
public class Bar
{
public Baz Baz { get; set; } = null!;
}
public class Baz
{
}
```
##### Bad
[link](https://sharplab.io/#v2:CYLg1APgAgzABFATHACgUwE4GcD2A7AWACgBvYuChedbfACgEpzKyjL24BJPASwBdGAbmYUAviLgTYCAIwAGOADEe2PgDkAhgFs0cEnADmaPoLhZjp0ZLaVpUeQH44AWR7BgAGzSadewxbMA8RsKO3k4ABkNLHVtXX0jE0CkqwkpABYuXgEGa3ZWDkplVR9dAF44ACJFHBxK0wl2KJjSuArKgCENAC96iWDRIA==)
```C#
public class Person
{
public Person() // warning CS8618: Non-nullable property 'FirstName' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.
{ // warning CS8618: Non-nullable property 'LasName' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.
Init();
}
public string FirstName { get; set; }
public string? MiddleName { get; set; }
public string LastName { get; set; }
void Init()
{
FirstName = "Foo";
LastName = "Baz";
}
}
```
##### Good
[link](https://sharplab.io/#v2:CYLg1APgAgDABFAjAOgCIEsCGBzAdgewGcAXdAY0OQGF9gBTAQV0wBsBPQ9QgbgFgAoAVADMCAExwACnQBOhfLgEBvAXDUJR0uQoAUASlXqV/dabgBJXOmL6+J9QF9Da5xoSJ4AMXRziAOUwAWzo4JThsOmJuOEJI6Ic4VxF3GAB+OABZdGBgFjoA4NDwuJiSp3s1ZKR4ABlMEgKQsIio0taEgVcAbQy6QIAjWT98fwBXFhYdZmD8ADMdb19GvQAaOGm6OZ06hqC6PT0AXSSAFgsrGz1EitDXU0XdwoBeOAAiT3x8V+i79R3/PZwF6vABCmAAXt9XOUHEA==)
```C#
using System.Diagnostics.CodeAnalysis;
public class Person
{
public Person() // ok
{
Init();
}
public string FirstName { get; set; }
public string? MiddleName { get; set; }
public string LastName { get; set; }
[MemberNotNull(nameof(FirstName), nameof(LastName))]
void Init()
{
FirstName = "Foo";
LastName = "Baz";
}
}
```
##### Bad
[link](https://sharplab.io/#v2:C4LgTgrgdgNAJiA1AHwAICYCMBYAUHgWQAooBTAdyIEoACAbxoDEBLMAZ2ADkBDAW1JoBeGgCJGAe3EiYNAszhwANqR78hogELcAXtJoAZbh1UDhIrWBE0AvlQDcePKkwA2GqgAssogAVS7cSgaAAcqPDo8GiiaZgAzGiJggDo5BWUTGgBCYSgIRUUw3Gj6SOLo5IAlUjZSYABBfJZSRTg2agcisqjnAE5ElPklFT5SJP1SKABzYAALe1Ko6zwl/FxUAGZ3dBo/AKhwhfdN5wAGJlZjEfoaSdq7Ghrge+t1XPzMjuKN90wTgH5ZIN0lcGLcng87jZDt9TgYjFwQTdIY9nq88ooPodoccvFVHg1FE0Wm1aIIAHyAtLDNQ5dEdFZAA)
```C#
using System;
M(new() { FirstName = "Foo", MiddleName = "Baz", LastName = "Bar" });
static void M(Person p)
{
if (p.MiddleName != null)
{
p.ResetAllFields(); // can't detect the change!
Console.WriteLine(p.MiddleName.Length); // NullReferenceException
}
}
public class Person
{
public string FirstName { get; set; } = null!;
public string? MiddleName { get; set; }
public string LastName { get; set; } = null!;
public void ResetAllFields() => MiddleName = null;
```
##### Good
[link](https://sharplab.io/#v2:C4LgTgrgdgNAJiA1AHwAICYCMBYAUHgWQAooBTAdyIEoACAbxoDEBLMAZ2ADkBDAW1JoBeGgCJGAe3EiYNAszhwANqR78hogELcAXtJoAZbh1UDhIrWBE0AvlQDcePKkwA2GqgAssogAVS7cSgaAAcqPDo8GiiaZgAzGiJggDo5BWUTGgBCYSgIRUUw3Gj6SOLo4PUAcVJgAEEocWAAC38/AKhqOzLu6NLu5wBORJT5JRU+UiT9UigAc2b7Pps8a0dcZzc2tkCaarqG5tb/bY7aQQA+GjJyGi2d8JollnYuCfUxSWklw2M3swsRCsHPh1gBmdzoW7HQLhJaocHOAAMTFYvzUDFmNS6bCxNnUuXymWBxXh7kwiIA/LJRuk3hjcTjgF1VkVoqSkQYjK90TRMUyaIzmfi8ooiSs8EA=)
```C#
using System;
M(new() { FirstName = "Foo", MiddleName = "Baz", LastName = "Bar" });
static void M(Person p)
{
if (p.MiddleName != null)
{
p = GetAnotherPerson(); // detects the change!
Console.WriteLine(p.MiddleName.Length); // warning CS8602: Dereference of a possibly null reference.
}
}
static Person GetAnotherPerson() => new Person
{
FirstName = "Foo",
LastName = "Bar"
};
public class Person
{
public string FirstName { get; set; } = null!;
public string? MiddleName { get; set; }
public string LastName { get; set; } = null!;
public void ResetAllFields() => MiddleName = null;
```