This page shows the source for this entry, with WebCore formatting language tags and attributes highlighted.

Title

When [NotNull] is null

Description

I prefer to be very explicit about nullability of references, wherever possible. Happily, most modern languages support this feature non-nullable references natively (e.g. TypeScript, Swift, Rust, Kotlin). As of version 8, C# also supports non-nullable references, but we haven't migrated to using that enforcement yet. Instead, we've used the JetBrains nullability annotations for years.<fn> Recently, I ended up with code that returned a <c>null</c> even though R# was convinced that the value could never be <c>null</c>. The following code <i>looks like</i> it could never produce a null value, but somehow it does. <code> [NotNull] <hl>// The R# checker will verify that the method does not return null</hl> public DynamicString GetCaption() { var result = GetDynamic() ?? GetString() ?? new DynamicString(); } [CanBeNull] private DynamicString GetDynamic() { ... } [CanBeNull] private string GetString() { ... } </code> So, here we have a method <c>GetCaption()</c> whose result can never be <c>null</c>. It calls two methods that <i>may</i> return <c>null</c>, but then ensures that its own result can <i>never</i> be null by creating a new object if neither of those methods produces a string. The nullability checker in ReSharper is understandably happy with this. At runtime, though, a call to <c>GetCaption()</c> was returning <c>null</c>. How can this be? <h>The Culprit: An Implicit Operator</h> There is a bit of code missing that explains everything. A <c>DynamicString</c> declares <i>implicit operators</i> that allow the compiler to convert objects of that type to and from a <c>string</c>. <code> public class DynamicString { // ...Other stuff [CanBeNull] public static implicit operator string([CanBeNull] DynamicString dynamicString) => dynamicString?.Value; } </code> A <c>DynamicString</c> contains zero or more key/value pairs mapping a language code (e.g. "en") to a value. If the object has no translations, then it is equivalent to <c>null</c> when converted to a <c>string</c>. Therefore, a <c>null</c> or empty <c>DynamicString</c> converts to <c>null</c>. If we look at the original call, the compiler does the following: <ol> The call to <c>GetDynamic()</c> sets the type of the expression to <c>DynamicString</c>. The compiler can only apply the <c>??</c> operator if <i>both sides are of the same type</i>; otherwise, the code is in error. Since <c>DynamicString</c> can be <i>coerced</i> to <c>string</c>, the compiler decides on <c>string</c> for the type of the first coalesced expression. The next coalesce operator (<c>??</c>) triggers the same logic, coercing the right half (<c>DynamicString</c>) to the type it has in common with the left half (<c>string</c>, from before). Since the type of the expression must be <c>string</c> in the end, even if we fall back to the <c>new DynamicString()</c>, it is coerced to a <c>string</c> and thus, <c>null</c>. </ol> Essentially, what the compiler builds is: <code> var result = (string)GetDynamic() ?? GetString() ?? (string)new DynamicString(); </code> The R# nullability checker sees only that the final argument in the expression is a <c>new</c> expression and determines that the <c>[NotNull]</c> constraint has been satisfied. The compiler, on the other hand, executes the final cast to <c>string</c>, converting the empty <c>DynamicString</c> to <c>null</c>. <h>The Fix: Avoid Implicit <c>DynamicString</c>-to-<c>string</c> Conversion</h> To fix this issue, I avoided the <c>??</c> coalescing operator. Instead, I rewrote the code to return <c>DynamicString</c> wherever possible and to implicitly convert from <c>string</c> to <c>DynamicString</c>, where necessary (instead of in the other direction). <code> public DynamicString GetCaption() { var d = GetDynamic(); if (d != null) { return d; } var s = GetString(); if (s != null) { return s; <hl>// Implicit conversion to DynamicString</hl> } return GetDefault(); } </code> <h>Conclusion</h> The takeaway? Use features like implicit operators sparingly and only where absolutely necessary. A good rule of thumb is to define such operators only for <c>structs</c> which are values and can never be <c>null</c>. I think the convenience of being able to use a <c>DynamicString</c> as a <c>string</c> outweighs the drawbacks in this case, but YMMV. <hr> <ft>Java also has <c>@NonNull</c> and <c>@Nullable</c> annotations, although it's <a href="https://stackoverflow.com/questions/4963300/which-notnull-java-annotation-should-i-use" source="StackOverflow">unclear which standard you're supposed to use.</a></ft>