35

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?

Jeremy Caney
  • 7,102
  • 69
  • 48
  • 77
M.Hassan
  • 10,282
  • 5
  • 65
  • 84
  • 5
    When you do construction initialization the way you show, the default constructor runs (initializing ServiceUrl to its default value (which, for strings, is null)). Then, the initialize statement runs. If you want it non-nullable, it needs to exit all constructors initialized – Flydog57 Nov 30 '19 at 16:58
  • POCO doesn't mean it has no constructor or that the properties aren't initialized. It means `Plain Old C# Object`. Just an object like any other, without inheriting from any special class. I suspect you have a *different* question. How to create DTOs - `Data Transfer Objects` – Panagiotis Kanavos Dec 02 '19 at 08:19
  • 1
    `This should be the responsibility of the one initializing the class to ensure that the properties are non-null` the class should be in a valid state always. If `null` isn't allowed, the property should *never* be null. Perhaps, instead of a basic string you should use a specialized class for the URL that can have a value of `None` or` Missing`, like the Option class in F#. C# 8 allows you to write such classes and check them with pattern matching – Panagiotis Kanavos Dec 02 '19 at 08:25
  • @Panagiotis Kanavos, The class,in my case, has a constraint to be parameter-less Constructor. So I have to use Property Initializer: `public string ServiceUrl { get; set; } = default! ;`. I hope Roslyn may have in the future a way to handle Late initialization outside the scope of ctor. I was using MayBe (like Option class), but I switched to nullable Reference Type in c#8 for the new code. – M.Hassan Dec 02 '19 at 13:04
  • 1
    NRTs aren't Maybes, in fact, Maybes are now even *more* important and easier to use. Provided of course they're build using C# idioms that make them easy to work with pattern matching – Panagiotis Kanavos Dec 02 '19 at 13:08
  • Check [this answer for example](https://stackoverflow.com/questions/58648767/how-to-deal-with-optional-arguments-when-wanting-to-enable-nullable-reference-ty/58663401#58663401). Creating an Option is now easy, and the value can be used with pattern matching directly. Using `struct` for the Option type allows using `default` as a quick way to generate `None` – Panagiotis Kanavos Dec 02 '19 at 13:11

9 Answers9

38

You can do either of the following:

  1. EF Core suggests initializing to null! with null-forgiving operator

    public string ServiceUrl { get; set; } = null! ;
    //or
    public string ServiceUrl { get; set; } = default! ;
    
  2. Using backing field:

    private string _ServiceUrl;
    public string ServiceUrl
    {
        set => _ServiceUrl = value;
        get => _ServiceUrl
               ?? throw new InvalidOperationException("Uninitialized property: " + nameof(ServiceUrl));
    }
    
KyleMit
  • 30,350
  • 66
  • 462
  • 664
M.Hassan
  • 10,282
  • 5
  • 65
  • 84
  • The question mentioned that he can't use the the `!` operator. – Athanasios Kataras Nov 30 '19 at 17:13
  • I can't use ! with type, but the link I mentioned show how to initialize using null!. It's a workaround solution. I modified the question. – M.Hassan Nov 30 '19 at 17:24
  • "_null!_" is a **(null-forgiving) operator** added in C# 8. A correct documentation reference would be https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/null-forgiving – Alex Klaus May 23 '20 at 09:57
  • 3
    I wish i could just write string! instead of string .. { get; set; } = null!, this is getting so repetitive.... or just mark the class that non of its properties are expected to be left null.. – Peter May 23 '20 at 19:58
  • 3
    I strongly disagree with EF Core's documentation's advice because it puts the object into an invalid state - using the `!` operator is just sweeping the problem under-the-rug, because hoping no-one ever tries to use a never-initialized `string` property in the object will cause an NRE. A better solution would be if EF Core supported primary-constructors and better-yet: if C# had better constructor ergonomics. – Dai Nov 02 '21 at 20:23
  • 4
    this is super annoying that we would have to manually do that with nullable enabled. extra work for not so much of a benefit. – ihor.eth Dec 22 '21 at 03:05
10

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

  1. either need to assign a string default value in the declaration

    public string ServiceUrl { get; set; } = String.Empty;

  2. Or initialize the value in the default constructor so that you will get rid of the warning

  3. Use the ! operator (that you can't use)

  4. Make it nullable as robbpriestley mentioned.

Athanasios Kataras
  • 25,191
  • 4
  • 32
  • 61
  • 7
    Or add a constructor accepting the service URL as a non-nullable string, of course. – Jon Skeet Nov 30 '19 at 17:28
  • `ServiceUrl'` can't be =String.Empty. There are other Properties of complex type that can't be null, so I used `default` like: 'public MyClass MyProperty { get; set; } = default! ' to stop the warning. This class (and others) is parameter-less constructor and is lately initialized outside the scope of ctor. – M.Hassan Dec 02 '19 at 13:13
9

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.

Jérôme MEVEL
  • 7,031
  • 6
  • 46
  • 78
  • It only seems to work in visual studio, not vs code. Maybe I'm missing something, as I'm quite new to .NET. – BeniaminoBaggins Mar 12 '21 at 09:17
  • @BeniaminoBaggins I never use VS Code for C# programming but I just opened my solution with it and it seems you're right, for some reasons this library doesn't work with VS Code. However I'm not sure about the state of VS Code for C# programming nowadays. It's showing me compile **errors** but my project compiles and debug fine. Doesn't really make sense. So maybe VS Code is not yet ideal for C# programming, I don't know... Consider opening a ticket on the [Github repository](https://github.com/cezarypiatek/CSharpExtensions), maybe the developer can actually fix this issue! – Jérôme MEVEL Mar 15 '21 at 08:50
  • Okay, thank you. Those errors are actually just warnings so that will explain why it will compile. Yes, I think a ticket could be raised with the NuGet package developers for this issue. Vs Code is pretty good for C#, I haven't run into other issues of this sort – BeniaminoBaggins Mar 16 '21 at 04:06
  • @BeniaminoBaggins no actually that's why I wrote **errors** in bold, because VS Code really shows me both some warnings and errors (not necessary related to nullable reference types but false positive of missing dependencies). That's why I was saying, I'm not sure about the current state of VS Code for C# programming... – Jérôme MEVEL Mar 16 '21 at 06:02
  • Oh really. Hmm then yea that’s weird and a justified concern about C# in VS Code – BeniaminoBaggins Mar 16 '21 at 16:55
  • This library do exactly what I wanted, thank you very much. – kb05 Sep 16 '21 at 22:58
  • 1
    @BeniaminoBaggins, as of April 2020, OmniSharp did not support this attribute. See [this GitHub comment](https://github.com/cezarypiatek/CSharpExtensions/issues/2#issuecomment-620886285) – Nathan Goings Mar 21 '22 at 23:08
  • 1
    We are increasing our solution complexity for nothing. The best solution still is to keep null-checking as many things as possible as you cannot know if the implementation you're using is null safe. My behaviour will remain "never trust that the code is null-safe, not even yours". – Michele Bortot Apr 06 '22 at 15:57
6

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
Dreamescaper
  • 175
  • 2
  • 5
3

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.

Shimmy Weitzhandler
  • 101,809
  • 122
  • 424
  • 632
3

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!;
ajbeaven
  • 9,265
  • 13
  • 76
  • 121
1

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.

  1. The variable has been definitely assigned to a non-null value.
  2. The variable or expression has been checked against null before de-referencing it.

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:

  1. Appended '?' before the variable (Example) string? Name
  2. Using "null forgiving operator" (Example) string Name {get;set;} = null!; or string Name {get;set;} = default!;
  3. 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:

  1. Better handling of null reference exceptions
  2. With the help of compile time warnings, developers can correct their code timely.
  3. Developers will convey the intent to compiler while design the class. Thereafter compiler will enforce the intent through out the code.
Thamizh
  • 29
  • 6
0

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.

Christoph
  • 3,322
  • 2
  • 19
  • 28
  • I think the settable is similar to using the backing field solution that throw exception if null when you access the property get. – M.Hassan Nov 22 '22 at 11:59
-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

  • OP has asked "My question: How to handle such errors in DTO classes when C#8's nullability context is enabled?". Empirically, your solution works. Unfortunately, there does not seem to be definite reference in any of the MSDN docs about using this approve or null forgiving operator. Perhaps you could elaborate? –  Jul 08 '21 at 12:55