Why in C# 10 do I get a compiler warning CS8618 on init properties
Asked Answered
P

3

11

I have this code

#enable nullable
public class SomeClass
{
  public string SomeProperty { get; init; }
}

With the following compiler warning:

[CS8618] Non-nullable property 'SomeProperty' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

I thought the whole point of declaring the property as init was to ensure the properties are set at creation time with the property initializer syntax, like

var s = new SomeClass { SomeProperty = "some value" };

So why do I get this compiler warning? Have I completely misunderstood what the init is for?

Edit 1:

I am not the only one being tripped by this. There is a Roslyn issue about almost the same thing.

There is also a proposal about required properties where Richiban asks:

What's the reasoning being init props being non-mandatory? I may have missed something, but I imagine that they'll be very rarely used without a req modifier.

There is a difference between 'can initialize' and 'must initialize', which the init keyword fails to communicate clearly. With all the fuzz around non-nullable reference types and correct code, I believed, incorrectly, that init meant 'must initialize'.

Primrose answered 18/12, 2021 at 11:10 Comment(0)
T
6

An init-only property doesn't require that properties are set at creation time with the property initializer syntax. It allows them to be set that way instead of requiring that all read-only properties are set by constructors.

So with your code,

var s = new SomeClass();

... is still perfectly valid, and would end up with s.SomeProperty being null, contrary to the nullability of its type.

Tocopherol answered 18/12, 2021 at 11:14 Comment(2)
I thought init was meant for that use case. So the compiler would complain about missing initialization. Because for the object consumer, the state after creation and initialization is what counts. Your example shouldn’t be valid code. I am disappointed.Primrose
@ThomasEyde Even if new SomeClass(); were invalid, you'd still have the same problem with new T(); in a generic class. If you want to ensure that the caller sets SomeProperty, I'd add a parameter to the constructor.Rejoinder
B
6

with init keyword the property value can only be changed in constructor or initilize block.

if you use the default constructor

var s = new SomeClass ();
s.SomeProperty = "A value";//Compiler error CS8852  Init-only property or indexer "SomeClass.SomeProperty" can only be assigned in an object initializer, or on "this" or "base" in an instance constructor or an "init" accessor.

The property SomeProperty was initialized with null, but is not nullable reference type. the compiler is reporting a possible future runtime error. Warning CS8618 Non-nullable property "SomeProperty" must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

If there is no problem that this happens, the warning can be removed with the following

1. Use null forgiving operator

public class SomeClass
{
    public string SomeProperty { get; init; } = null!;// indicating null value is a correct not null value. 
}

When you try to access and use the SomeProperty value, you need to check nullability to avoid a null reference exception even though the type indicates that it is non-nullable. This is irritating.

if(s.SomeProperty==null) //OK
{
   //DO SOME STUFF
}

var length = s.SomeProperty?.Length ?? 0; //OK

var length2 = s.SomeProperty;//NullReferenceException

2. Assigning/Initialize a valid value for property

public class SomeClass
{
    public string SomeProperty { get; init; } = "My initial value"; 
}

you can invoke default constructor without initialze the property
var s = new SomeClass (); //Some property is "My initial value"

or invoke constructor with initialization block

var s = new SomeClass()
{
   SomeProperty = "My new intial value"
};


var length = s.SomeProperty.Length; //NO exception / No problem

3. Create constructor with parameter, the default constructor is avoided.

Errors/Warnings only appear in the caller code.

public class SomeClass
{
    public string SomeProperty { get; init; } //ALL OK. CS8618 dissapeared / No Warning.

    public SomeClass(string someProperty)
    {
        SomeProperty = someProperty;
    }
}

var s = new SomeClass(); //CS7036 ERROR.


var s2 = new SomeClass() //CS7036 ERROR.
{
    SomeProperty = "My new intial value"
};

var s3 = new SomeClass("My new initial value"); //OK. No Warning

var s4 = new SomeClass("My new initial value") //OK. No Warning.
{
    SomeProperty = "My other new intial value"
};

s4.SomeProperty = "A value"; //CS8852 ERROR. SomeProperty is enabled to only be initialized in constructor or intialize block.
Boat answered 18/12, 2021 at 22:6 Comment(2)
The spirit of my question is why init doesn’t take care of the non-nullable problem. Jon Skeet explained init doesn’t work that way. I appreciate your suggestions on how to avoid compiler errors, but that was never my intention. I want those errors, but I want them at the appropriate time, which is to me after initialization. Also, giving default values only work when there are sensible defaults. In my case, there aren’t. A compiler valid value is necessarily not the same as a domain valid value.Primrose
Check the update, 3rd form. The errors appear when create an instance of class.Boat
B
5

As the edit and the other answers point out, this behaviour arises from the fact that init only makes it so that the object/value cannot be changed after initialization, which becomes weird if it's a property you don't allow to be null but at the same time allow for the parent to initialized without the said property.

With C# 11 and the introduction of Required Members you can now use the required keyword to specify that the member has to be initialized at creation, if that's what you wanted to achieve:

public class SomeClass
{
    public required string SomeProperty { get; set; }
}

Or, if you want to both demand that the property is set when the instance is created and that it becomes immutable, you can use it in combination with init:

public class SomeClass
{
    public required string SomeProperty { get; init; }
}
Blanka answered 10/5, 2023 at 14:2 Comment(1)
'wierd' is an understatement. I am aware of the required modifier, but I have yet to understand the reasoning behind it. It seems so much easier to simply fix the init issue.Primrose

© 2022 - 2024 — McMap. All rights reserved.