Last active
September 10, 2023 09:14
-
-
Save habib-sadullaev/1e4397c302aaaf83b5d5f4698bc94cd6 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
### 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; | |
``` |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
https://learn.microsoft.com/en-us/dotnet/core/extensions/options#:~:text=The%20following%20class%20binds,of%20its%20configuration%20section.
https://github.com/dotnet/docs/pull/18278/files
https://learn.microsoft.com/en-us/ef/core/miscellaneous/nullable-reference-types#non-nullable-properties-and-initialization
https://devblogs.microsoft.com/dotnet/nullable-reference-types-in-csharp/