DEV Community

Michael B
Michael B

Posted on

Prefer Empty Objects over Compiler tricks

Listen to the Compiler

There are certain situations in C# where you have nullable references that are not initialized in the constructor. The most common situation is Entity Framework navigation properties.

The following shows an Entity Framework entity "Airplane" that has a navigation property of "AircraftType".

public class Airplane
{
    // other fields and methods

    public AircraftType AircraftType { get; }
}
Enter fullscreen mode Exit fullscreen mode

This will produce a CS8618 - Resolve Nullable Warnings error. The compiler is telling us we have a nullable object that is leaving the constructor without being initialized. This will throw a null reference exception if we attempt to de-reference the object.

The issue is that we know that the object will be initialized, but the compiler does not.

The most common solution is to use the "default!" trick to tell the compiler to ignore this nullable situation.

public class Airplane
{
    public Airplane()
    {
        AircraftType = default!;
    }

    public AircraftType AircraftType { get; }
}
Enter fullscreen mode Exit fullscreen mode

This is not ideal due to silencing the compiler. We are leaving our application in a possible situation of undefined behavior. Unit testing or running the application without the database will yield possible errors. We need to ensure that de-referencing the property in any situation will not throw exceptions.

var airplane = new Airplane();

// Null reference exception will occur.
var aircraftType = airplane.AircraftType;
Enter fullscreen mode Exit fullscreen mode

Putting null checks everywhere in your code is not an ideal situation either. Due to each null check requiring a unit test against it.

You should adhere to a strict policy of putting the compiler first. Nothing, including you, is above it.

Instead of using the "default!" trick, use an object that contains empty data to satisfy the compiler. This is a variant of the null object pattern. Using an empty object will use default values in place of real-life data.

public class AircraftType
{
    // implementation

    public static AircraftType EmptyObject()
    {
        return new AircraftType(Guid.Empty(), string.Empty, 0);
    }
}
Enter fullscreen mode Exit fullscreen mode

Your other class will be updated like so:

public class Airplane
{
    public Airplane()
    {
        AircraftType = AircraftType.EmptyObject();
    }

    public AircraftType AircraftType { get; }
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

Using empty objects over silencing the compiler will lead to more consistent behavior in your application. Warnings from the compiler are a stern indicator that a change is needed. It is of the utmost importance to listen to what your application is telling you. Transforming your code to be implementation agnostic is one way to future-proof yourself and others on your team from future headaches.

Top comments (0)