Init-only reference properties with nullable enabled in C# 10.0
Asked Answered
P

2

24

I tried to use init-only properties to force client code to initialize my class when they create it, but without a constructor. It's not working as I planned.

Here's the class, stripped down to illustrate the point.

public class Target
{
    public int              Id               { get; init; }
    public string           Name             { get; init; }
}

The project is on .NET 6.0 so I'm using C# 10.0. Nullable is set to "Enabled" on the project so the reference property Name must be non-null. What confuses me is that that compiler complains that the Name property could be null after the constructor.

CS8618: Non-nullable property 'Name' must contain a non-null value when exiting constructor

This is true, of course, but the whole point of using an init-only property was that I don't want a constructor. I want to force people to use the init-only properties to initialize Name to something valid. If I wanted to write a constructor, I could just write a read-only property.

(I realize I could default Name to string.Empty or some other valid value myself but I want to force the coder to do that)

Does C# 10.0 give me a way to achieve what I want without doing any of the following?

  • defaulting a reference property to some non-null value (either by constructor or by initializer inline)
  • declaring the property as nullable
  • disabling nullable altogether
  • use a record type instead of a class (because I want member functions)

Is this doable?

My searching on this topic led me to this post but the best I could get from it was a link to Mads Torgensen's blog discussing what they were planning to do in C# 10. Is there an update on it?

Petard answered 20/12, 2021 at 22:38 Comment(0)
F
11

This is possible starting with C# 11 / .net 7 using the required keyword. For example:

public class Target
{
    public int Id { get; init; }
    public required string Name { get; init; }
}

Relevant section from the C# 11 release notes: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/required

Keyword documentation: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/required

Fanciful answered 23/6, 2023 at 14:36 Comment(2)
Brilliant. Looks to be just what I wanted!Petard
If you add a backing field to Name, you will still get a warning. Probably a bug?Johniejohnna
N
21

init properties do not force the values to be initialized, only constructors do. What init does is that, if the property is to be initialized, it must be done at construction time in an object initializer (or in the constructor if you have one): it does not guarantee that it will.

If you want maximum robustness here, initialize them through the constructor instead, which allows you to add guard clauses and guarantee non-nullable properties.

This is what you want (and I do as well):

This proposal adds a way of specifying that a property or field is required to be set during object initialization, forcing the instance creator to provide an initial value for the member in an object initializer at the creation site.

UPDATE:

required properties have been implemented in C# 11 and should be the way to go to enforce initialization and solve this issue cleanly.

Negrophobe answered 20/12, 2021 at 22:44 Comment(1)
Ah I see. This would be a nice addition, I thinkPetard
F
11

This is possible starting with C# 11 / .net 7 using the required keyword. For example:

public class Target
{
    public int Id { get; init; }
    public required string Name { get; init; }
}

Relevant section from the C# 11 release notes: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/required

Keyword documentation: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/required

Fanciful answered 23/6, 2023 at 14:36 Comment(2)
Brilliant. Looks to be just what I wanted!Petard
If you add a backing field to Name, you will still get a warning. Probably a bug?Johniejohnna

© 2022 - 2024 — McMap. All rights reserved.