-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Description
Problem
Consider the following three outstanding issues with NRTs
1. Object Initializers
Object initializers are greatly loved by many C# programmers. However they don't interact very well with NRTs.
Consider this for example:
public class C
{
public string Str {get; set;}
}
...
var c = new C { Str = "Hello World" };If Str is to be non-nullable, then when enabling nullability we are forced to completely change all this code:
public class C
{
public C(string str) => Str = str;
public string Str {get;}
}
...
var c = new C("Hello World");Making object initializers usable only be nullable properties and fields.
2. DTOs
Many DTOs are initialized by ORM/Serialization frameworks. As a result they often contain only property getters and setters, without any constructors.
This of course causes warnings when NRTs are enabled, as it doesn't know the properties are always initialized by the ORM/Serialization framework.
3. Structs
Structs always have a default constructor, which cannot be overridden.
As a result whenever a struct containing a field of a non-nullable reference type is constructed using the default constructor, the field will initially be null. This leaves a big hole in the nullable-type-system:
public struct S
{
public string Str { get; set; } //no warning
}
...
var s = new S(); //no warning
var l = s.Str.Length; // kabam! NullReferenceException! - but no warningSolution
Based on a suggestion by @Joe4evr #36 (comment)
The real issue here is that present mechanisms only allow us to guarantee a field is initialized inside the constructor, but currently many types rely on their invariants being upheld by whoever constructs the type.
It should be possible to mark that a field/auto-property must be initialized at the point it is constructed, before it is used. This could be done via an attribute for example:
public class C
{
[MustInitialise]
public string Str {get; set;}
}
...
public struct S
{
[MustInitialise]
public string Str { get; set; }
}Then when an instance of the type is constructed, it should be a warning to use the type before all such fields are initialised:
public class C
{
[MustInitialise]
public string Str {get; set;}
}
...
var c = new C { Str = "Hello World" };
var l = c.Str.Length; //no warning
c = new C();
var x = c.Str; //warning
c.Str = "Hello"
var l = c.Str.Length; //no warning
...
public struct S
{
[MustInitialise]
public string Str { get; set; }
}
...
var s = new S { Str = "Hello World" };
var l = s.Str.Length; //no warning
s = new S();
var x = s.Str; //warning
s.Str = "Hello"
var l = s.Str.Length; //no warningAll structs should have this attribute on all their fields implicitly.
Further issues
Consider this struct:
public struct S
{
public S(string str) => Str =str;
public string Str { get; set; }
}Then it would be a warning not to initialize S.Str when the the new S(string) constructor was called, even though S.Str was initialized by the constructor!
I think the solution to this would be for the compiler to detect which constructors initialize a field, and implicitly add parameters to the MustInitialize attribute which indicate which constructors did not initialize the struct.
ie. the compiler should generate code equivalent to the following:
public struct S
{
public S(string str) => Str =str;
[MustInitialize("DefaultConstructor")]
public string Str { get; set; }
}then, when the default constructor is called, the compiler knows it must make sure S.Str is initialized.