Description
Version Used: 05/14/18 Nullable Reference Types Preview (csc
reports 2.8.0.62830 (e595ee27)
) with Visual Studio 15.7.1
Demonstration Code:
public abstract class AbstractBaseClass
{
public string Property0 { get; protected set; }
public string Property1 { get; protected set; }
public string Property2 { get; private set; }
public string Property3 { get; protected set; }
// warning CS8618: Non-nullable property 'Property0' is uninitialized.
// warning CS8618: Non-nullable property 'Property1' is uninitialized.
// warning CS8618: Non-nullable property 'Property2' is uninitialized.
public AbstractBaseClass()
=> Property3 = "3";
}
public class ChildClass : AbstractBaseClass
{
public string Property4 { get; private set; }
// warning CS8618: Non-nullable property 'Property4' is uninitialized.
public ChildClass()
=> Property0 = "0";
}
Expected Behavior:
The constructor for AbstractBaseClass
has a warning for Property2
and no others.
The constructor for ChildClass
has a warning for Property1
and Property4
.
Actual Behavior:
(The warnings received are as displayed in the comments in the example code.)
The constructor for AbstractBaseClass
has a warning for Property0
, Property1
, and Property2
despite ChildClass
being capable of initializing Property0
and Property1
.
The constructor for ChildClass
only receives a warning for Property4
This is potentially non-trivial since the abstract class could lie within another assembly. I feel like the easiest solution would be to add an attribute [DefinitelyInitialized]
to fields/properties that are appropriately initialized by the abstract constructors. However, I don't know how this would handle multiple constructors since you could initialize different subsets of properties in different constructors.
Some people might consider relying on child implementations to perform initialization to be evil, but I'd consider it to be in the same realm as the problem described on the wiki page with indirect initialization.
In our use-cases, creating the object needed by the base class is too complicated* to do before the base constructor is called, so we rely on child implementations assigning the property themselves.
(*In particular, we want to do some argument validation before constructing the object because it is a native resource and we don't want to leak its handle if we throw an exception after associating it with an IDisposable
.)
Activity