Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# non-nullable field: Lateinit?

I am wondering how to use late-initialized class fields in C# with nullable reference types. Imagine the following class:

public class PdfCreator { 

   private PdfDoc doc;

   public void Create(FileInfo outputFile) {
       doc = new PdfWriter(outputFile);
       Start();
   }

   public void Create(MemoryStream stream) {
       doc = new PdfWriter(stream);
       Start();
   }

   private void Start() {
      Method1();
      // ...
      MethodN();
   }

   private void Method1() {
      // Work with doc
   }

   // ...

   private void MethodN() {
      // Work with doc
   }
}

The above code is very simplified. My real class uses many more fields like doc and also has some constructors with some arguments.

Using the above code, I get a compiler warning on the constructor, that doc is not initialized, which is correct. I could solve this by setting the type of doc to PdfDoc?, but then I have to use ?. or !. everywhere it is used, which is nasty.

I could also pass doc as a parameter to each method, but remember that I have some fields like this and this violates the clean code principle in my eyes.

I am looking for a way to tell the compiler, that I will initialize doc before using it (actually I do it, there is no possibility for the caller to get a null reference exception!). I think Kotlin has the lateinit modifier exactly for this purpose.

How would you solve this problem in "clean" C# code?

like image 322
Andi Avatar asked Dec 18 '22 13:12

Andi


2 Answers

Best solution I found so far is this one:

private PdfDoc doc = null!;

This removes all compiler warnings by using the null-forgiving operator introduced in C# 8. It allows you to use a value as if it were not null. Therefore, one way it can be used is when you need something similar to Kotlin's "lateinit". Unlike Kotlin's lateinit, it will actually get initialized to null here, which will be allowed by both the compiler and runtime. If you later use this variable where null is not expected, you can get a NullReferenceException, and the compiler will not warn you that it could be null, as it will think it is not null. Kotlin's lateinit has a subtle difference where if you accessed a lateinit property before it was initialized, it throws a special exception that clearly identifies the property being accessed and the fact that it hasn't been initialized.

like image 132
Andi Avatar answered Dec 24 '22 01:12

Andi


Late initialization can be tricksy with nullable reference types

One option is to make the member variable nullable and add a function to wrap the access:

private PdfDocument? pdfDocument = null;

private PdfDocument GetDocument()
{
  if(pdfDocument == null) throw new InvalidOperationException("not initialized");

  return pdfDocument;
}

Note that the compiler doesn't warn on this method, because it recognizes that the method will only return if pdfDocument is not null.

With this is place you can now change your methods to this:

private void Method1() 
{
  var doc = GetDocument();

  // doc is never null
}

Now your code more accurately models the intent. pdfDocument can be null, even if just for a short time, and your methods can access the document knowing they will never get back null.

like image 44
Sean Avatar answered Dec 24 '22 02:12

Sean