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?
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.
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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With