Introduction
C# nullable reference types
Since C# 8.0, we can use the "nullable reference types" feature on C# programming.
It helps us to build more robust applications on C#.
Adding the <Nullable>enable</Nullabl>
MSBuild property value into the C# project file (.csproj ) is one of the ways to enable this feature.
<!-- This is .csproj file -->
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
...
<!-- 👇 Add this line -->
<Nullable>enable</Nullable>
....
Code-behind in Blazor programming
In Blazor programming, to implement a component, we usually write an HTML markup and C# code mixing in a Razor file (.razor), and place a C# code inside a @code { }
block.
<!-- This is MyComponent.razor file -->
<p>Here is an HTML markup.</p>
@code {
// Here is a C# code block.
}
And another way we can use the "code-behind" way.
In this way, we write a C# code in a usual C# source file (.cs) as a partial class.
// This is MyComponent.razor.cs file
public class partial MyComponent
{
// Here is a C# code separated from the .razor file
}
Sometimes the "code-behind" style is convenient to make a Blazor component readable and maintainable.
Dependency Injection in a "code-behind" of a Blazor component
We have to use the @inject
directive in a Blazor component (.razor) to retrieve any service from the DI container.
<!-- This is MyComponent.razor file -->
<!-- 👇 The "IJSRuntime" service instance will be injected
from DI container. -->
@inject IJSRuntime JS
@inject
directives work as property injection.
So if you want to inject a service in a "code-behind" C# source file, you can do it with a property that is annotated with [Inject]
attribute instead of the @inject
directive.
// This is MyComponent.razor.cs file
public class partial MyComponent
{
// 👇 The "IJSRuntime" service instance will be injected
// from DI container.
[Inject]
private IJSRuntime JS { get; set; }
...
What happens if we mix these 3 programming styles?
If we mix these three programming factors - C# nullable reference type, code-behind style, property injection in code-behind - what will happen?
The answer is, it causes a terrible warning, "CS8618".
"Warning CS8618 Non-nullable property 'JS' must contain a non-null value when exiting constructor. Consider declaring the property as nullable."
However, in my understanding, the property that will be injected from the DI container might never be null where the code place that the application developer will write.
So I feel this CS8618 warning does not make sense.
What can we do to avoid this warning?
I have three ideas (but are not perfect solution) + [May 2, 2021, Updated] One more good idea I heard from others
I'm not sure about the perfect solution to this problem at this time.
But I came up with three ideas to mitigate the problem.
[May 2, 2021, Updated] I heard one more good idea from @taks.
So I added that method I heard into the list below.
1. Make the property nullable.
One of the ideas is to make the property nullable.
To do this, append the "?" symbol to the end of the type name.
[Inject] // 👇 "?" means this property can be null.
public IJSRuntime? JS { get; set; }
Instead, we have to do a null-check whenever before referencing the property.
// 👇 It required to perform null-check.
if (this.JS != null)
{
await this.JS.InvokeVoidAsync("foo");
}
(If we do not this, the C# compiler raise another warning "CS8604".)
This solution is strictly the right way.
But as I said before, the injected property will never be null in the developer's code as a practical matter.
So I feel this solution is too redundant.
We also can use the "!" symbol to make the C# compiler ignore the condition that the variable is null or not.
// 👇 The "!" symbol suppresses the null state check.
await this.JS!.InvokeVoidAsync("foo");
But I do not prefer this way because this way loses the benefits of the nullable reference type feature.
2. Add [AllowNull] attribute
The next of the ideas is to make the property is annotated with the [AllowNull]
attribute.
// 👇 Add the [AllowNull] attribute
[Inject, AllowNull]
public IJSRuntime JS { get; set; }
This solution almost works well ― except the property is acceptable to the null even it is a non-nullable type.
// 👇 There is no compiler error even if set to null.😥
this.JS = null;
3. Use "#pragma waning disable"
The last of the ideas is to make the property is surrounded by the "warning disable" pragma.
// 👇 Surround the property by the "warning disable" pragma
#pragma warning disable CS8618
[Inject]
public IJSRuntime JS { get; set; }
#pragma warning restore CS8618
This solution also works fine.
And, I think this solution is reflected what the developer wants to do most directly.
But this solution breaks the indent of the C# code, and the description volume is a little bit much, so the code loses readability.
Due to this, I do not prefer this solution.
4. [May 2, 2021, Updated] Use a null
value with the !
symbol to initialize the property
I did not know until I heard that, but we can initialize the non-nullable reference type variable by null value with the !
symbol, like this:
[Inject]
public IJSRuntime JS { get; set; } = null!;
// 👆 "null" with the "!" symbol
// can be set to non-nullable
// reference type property.
This implementation suppresses the CS8618 warning because the property is initialized, even the initial value is null.
And in my understanding, this solution doesn't have more side-effects any non-desirable.
So this solution became my best favorite solution.
Conclusion
In my job and hobby, I often use the first solution out of the above 3.
But I am significantly dissatisfied with every solution above because those are not smart.
I hope a near future C# compiler and syntax resolve this annoying problem smartly.
[May 2, 2021, Updated] As I said before, using the null
value with the !
symbol to initialize the property became my best solution at this time.
Actually, I use another solution too. That is, I don't mix it.
This means I use code-behind style, but I write the "@inject" directive in .razor files, do not write the properties that will be injected from DI container in .cs files, like this code:
<!-- This is MyComponent.razor file -->
<!-- 👇 Inject the service in .razor file ... -->
@inject IJSRuntime JS
// This is MyComponent.razor.cs file
public partial class MyComponent {
...
// ... and use it in code-behind C# file.
await this.JS.InvokeVoidAsync("foo");
If somebody knows a good solution for this problem, I appreciate it if you let me know via comment on this post.
Happy coding! :)
Top comments (2)
What about
public IJSRuntime JS { get; set; } = default!;
Would that be any better thanpublic IJSRuntime JS { get; set; } = null!;
?in C#11, you can now add the
required
keyword to your fields, which feels like a better workaround than any other the others listed so far:learn.microsoft.com/en-us/dotnet/c...