I have an abstract class as a base class for various implementations.
Let it look like this:
public abstract class MyBase {
public object Property1 { get; protected set; }
}
It is important, that the type of Property1
is object
and not object?
.
I want it to be completely clear that the Property1
is never null in implementation instance.
Let's say a typical implementation looks like this:
public sealed class MyImpl : MyBase {
public MyImpl() {
Property1 = new object();
}
}
Of course in real world there are plenty of properties in the base class and they are of various types, not just object
. I could mark the properties as abstract
, but then I would have to implement them in each implementing class - that would mean more boilerplate code with no real benefit in that, right?
OK, but now I get a warning, that the properties must have non-null values before exiting constructor - CS8618. Of course I can disable the warning for my base class, but is there a more elegant way to instruct the compiler that the implementing class MUST set those properties to non-null values instead of disabling the warning?
It would be perfect if I get the warning not for the base class, but for the implementation if it doesn't set one of those properties.
As I already figured out - the latest C# compiler and Visual Studio has plenty of magic special attributes that change the nullable warning behavior, but I don't know where to look.
CodePudding user response:
It is important, that the type of Property1 is object and not object?. I want it to be completely clear that the Property1 is never null in implementation instance.
The only way to guarantee that is with a protected
constructor in MyBase
that requires a value for Property1
or by making Property1
an abstract
property.
In .NET, when a subclass is instantiated, first its supertype's constructor runs to completion, and then the constructor of the next child type, and so on. There is currently no provision in .NET for a supertype to have a "post-constructor" that runs after the most-derived-type's constructor runs (I wish there was though..), so there is no way for MyBase
class to prove to the compiler that Property1
will be in a valid state when the object is constructed. Having a protected set
isn't any kind of provable guarantee (at least as far as the compiler is concerned): it's the OOP equivalent of a subclass saying "I pinkie-swear to set this property in my constructor!" and the rest of your program has to trust that verbal promise.
This is why the nullable annotations since C# 8.0 are so important and so useful: because now we no-longer need to operate on mere trust: we can now have the compiler actually verify that a field/auto-property is actually assigned to a non-null value, thus preventing any NullReferenceException
(everyone's favourite kind of exception!) from ever being thrown in the first place.
The fact the non-abstract
subclass is sealed
doesn't matter to the compiler in this case because your MyBase
could still be derived separately by a misbehaving subclass that never sets Property1
.
Of course in real world there are plenty of properties in the base class and they are of various types, not just
object
. I could mark the properties asabstract
, but then I would have to implement them in each implementing class - that would mean more boilerplate code with no real benefit in that, right?
I appreciate that having to copy paste or manually keystroke-in repetitive members is annoying, but doing so is not "boilerplate" if (re)implementing those abstract
members is expressing something meaningful in your business/domain model.
I get a warning that the properties must have non-null values before exiting constructor - CS8618. Of course I can disable the warning for my base class, but is there a more elegant way to instruct the compiler that the implementing class MUST set those properties to non-null values instead of disabling the warning?
Just use a protected
constructor. That's exactly what they're for.
Like this:
public abstract class MyBase
{
protected MyBase(object property1Value)
{
this.Property1 = property1Value ?? throw new ArgumentNullException(nameof(property1Value));
}
public object Property1 { get; }
}
That way all subclasses will be safe:
public sealed class Derived : MyBase
{
public Derived()
: base( GetProperty1FromAStaticFactory() )
{
}
}
or
public sealed class Derived : MyBase
{
public Derived( Object p1Value )
: base( p1Value )
{
}
}
CodePudding user response:
you are probably using net 6. Just comment or remove nullable option from a project properties. You will get rid of many problems and warnings
<!--<Nullable>enable</Nullable>-->
Some of people thinks that it is usefull but I doubt since you can start to make nullable all properties of all your classes in the project manually.
so alternative
public object? Property1 { get; protected set; }
that even looks very ridiculous, since a reference type is nullable in all another languages by default.