Reputation: 11032
I activated this feature in a project having data transfer object (DTO) classes, as given below:
public class Connection
{
public string ServiceUrl { get; set; }
public string? UserName { get; set; }
public string? Password { get; set; }
//... others
}
But I get the error:
CS8618
: Non-nullable property 'ServiceUrl' is uninitialized. Consider declaring the property as nullable.
This is a DTO class, so I'm not initializing the properties. This will be the responsibility of the code initializing the class to ensure that the properties are non-null.
For example, the caller can do:
var connection = new Connection
{
ServiceUrl=some_value,
//...
}
My question: How to handle such errors in DTO classes when C#8's nullability context is enabled?
Upvotes: 37
Views: 27427
Reputation: 7804
I've been playing with the new Nullable Reference Types
(NRT) feature for a while and I must admit the biggest complain I've is that the compiler is giving you those warnings in the classes' declarations.
At my job I built a micro-service trying to solve all those warnings leading to quite a complicated code especially when dealing with EF Core, AutoMapper and DTOs that are shared as a Nuget package for .NET Core consumers. This very simple micro-service quickly became a real mess just because of that NRT feature leading me to crazy non-popular coding styles.
Then I discovered the awesome SmartAnalyzers.CSharpExtensions.Annotations Nuget package after reading Cezary Piątek's article Improving non-nullable reference types handling.
This Nuget package is shifting the non-nullbable responsibility to the caller code where your objects are instantiated rather than the class declaration one.
In his article he is saying we can activate this feature in the whole assembly by writing the following line in one of your .cs
files
[assembly: InitRequiredForNotNull]
You could put it in your Program.cs
file for example but I personally prefer to activate this in my .csproj
directly
<ItemGroup>
<AssemblyAttribute Include="SmartAnalyzers.CSharpExtensions.Annotations.InitRequiredForNotNullAttribute" />
</ItemGroup>
Also I changed the default CSE001 Missing initialization for properties
errors to warnings by setting this in my .editorconfig
file
[*.cs]
dotnet_diagnostic.CSE001.severity = warning
You can now use your Connection
class as you'd normally do without having any error
var connection = new Connection()
{
ServiceUrl = "ServiceUrl"
};
Just one thing to be aware of. Let's consider your class like this
public class Connection
{
public string ServiceUrl { get; }
public string? UserName { get; }
public string? Password { get; }
public Connection(string serviceUrl, string? userName = null, string? password = null)
{
if (string.IsNullOrEmpty(serviceUrl))
throw new ArgumentNullException(nameof(serviceUrl));
ServiceUrl = serviceUrl;
UserName = userName;
Password = password;
}
}
In that case when you instantiate your object like
var connection = new Connection("serviceUrl");
The SmartAnalyzers.CSharpExtensions.Annotations
Nuget package isn't analyzing your constructor to check if you're really initializing all non-nullable reference types. It's simply trusting it and trusting that you did things correctly in the constructor. Therefore it's not raising any error even if you forgot a non-nullable member like this
public Connection(string serviceUrl, string? userName = null, string? password = null)
{
if (string.IsNullOrEmpty(serviceUrl))
throw new ArgumentNullException(serviceUrl);
// ServiceUrl initialization missing
UserName = userName;
Password = password;
}
I hope you will like the idea behind this Nuget package, it had become the default package I install in all my new .NET Core projects.
Upvotes: 9
Reputation: 9562
If you're using C# 11 or above, required members provide a solution to this:
public required string ServiceUrl { get; set; }
If you haven't yet upgraded to C# 11, use the null-forgiving operator instead:
public string ServiceUrl { get; set; } = null!;
Upvotes: 9
Reputation: 3642
I created a helper struct
that defines whether a value was set or not (independent of whether it is null
or not), so your DTO would look like this:
public class Connection
{
public Settable<string> ServiceUrl { get; set; }
public Settable<string?> UserName { get; set; }
public Settable<string?> Password { get; set; }
//... others
}
As a struct
is always initialized, no nullable reference type warnings appear, and if you attempt to access an uninitialized property, an InvalidOperationException
is thrown.
Details see NuGet package Hafner.Tools.Settable
(https://www.nuget.org/packages?q=hafner.tools.settable) and Git repo https://github.com/HugoRoss/Hafner.Tools.Settable.
Upvotes: 0
Reputation: 1
To get rid of the warnings on DTOs, at the start of the DTO's cs file, specify: #pragma warning disable CS8618
And at the end: #pragma warning restore CS8618
Upvotes: -1
Reputation: 11032
You can do either of the following:
EF Core suggests initializing to null!
with null-forgiving operator
public string ServiceUrl { get; set; } = null! ;
//or
public string ServiceUrl { get; set; } = default! ;
Using backing field:
private string _ServiceUrl;
public string ServiceUrl
{
set => _ServiceUrl = value;
get => _ServiceUrl
?? throw new InvalidOperationException("Uninitialized property: " + nameof(ServiceUrl));
}
Upvotes: 39
Reputation: 195
Usually DTO classes are stored in separate folders, so I simply disable this diagnostic in .editorconfig file based on path pattern:
[{**/Responses/*.cs,**/Requests/*.cs}]
# CS8618: Non-nullable field is uninitialized. Consider declaring as nullable.
dotnet_diagnostic.CS8618.severity = none
Upvotes: 6
Reputation: 129
The benefit of Nullable reference types is to manage null reference exceptions effectively. So it is better to use the directive #nullable enable. It helps developers to avoid null reference exceptions at run time.
How it helps to avoid null reference exceptions: The below 2 things compiler will make sure during static flow analysis.
If does not satisfy the above conditions, compiler will throw warnings. Note : All these activities emitted during compile time.
What is the special in null reference types:
After C# 8.0, reference types are considered as non nullable. So that compiler throw timely warnings if the reference type variables are not handle the nulls properly.
What we can do, if we know that reference variables are NULLABLE:
Using "backing fields" (Example):
private string _name; public string Name {get { return _name; } set {_name = value;} }
What we can do, if we know that reference variables are NON-NULLABLE:
If it is non nullable initialize the property using constructors.
Benefits of non-nullable reference types:
Upvotes: 1
Reputation: 104711
Another thing that might come handy in some scenarios:
[SuppressMessage("Compiler", "CS8618")]
Can be used on top of member or whole type.
Yet another thing to consider is adding #nullable disable
on top of file to disable nullable reference for the whole file.
Upvotes: 3
Reputation: 26352
If it's non nullable, then what can the compiler do when the object is initialized?
The default value of the string is null, so you will
either need to assign a string default value in the declaration
public string ServiceUrl { get; set; } = String.Empty;
Or initialize the value in the default constructor so that you will get rid of the warning
Use the !
operator (that you can't use)
Make it nullable as robbpriestley mentioned.
Upvotes: 10