Null and Undefined
| C# Type | JS Type |
|---|---|
null | undefined |
null | null |
In summary, a .NET null values is mapped to the JS undefined value (not JS null), while both JS undefined and null map to .NET null. The rest of this page explains the reasoning behind the design choice.
Consider:
- JavaScript has both
nullandundefinedprimitive values, while .NET has onlynull. - A default/uninitialized value in JS is
undefined, while in .NET the default isnull(for reference types andNullable<T>value types). - In JavaScript,
typeof undefined === 'undefined'andtypeof null === 'object'.
So how should these inconsistencies in type systems of the two platforms be reconciled during automatic marshalling?
Note: Instead of relying on automatic marshalling, .NET code has the option to work directly with
JSValueand its distinctJSValue.NullandJSValue.Undefinedvalues.
Marshalling .NET null to JavaScript
For discussion here, T may any specific (not object/any) type, including both marshal-by-value (number, struct, enum) and marshal-by-ref (string, class, interface) types.
If T is not nullable (neither a Nullable<T> value type nor a nullable reference type), then the TypeScript projection will allow neither null nor undefined. However, marshalling must still handle null .NET reference values even when the type is non-nullable.
In any case a JS undefined value passed to .NET is always converted to null by the marshaller.
Option A: .NET null -> JS undefined
If .NET null values are marshalled as JS undefined, that has the following effects:
| Description | C# API | JS API | JS Notes |
|---|---|---|---|
| Method with optional param | void Method(T? p = null) | method(p?: T): void | p is undefined in JS if a .NET caller omitted the parameter or supplied null;p is never null in JS when called by .NET. |
| Method with nullable param | void Method(T? p) | method(p: T | undefined): void | p is undefined in JS if a .NET caller supplied null. |
| Method with nullable return | T? Method() | method(): T | undefined | Result is undefined in JS if .NET method returned null;result is never null when returned by .NET. |
| Nullable property | T? Property | property?: T | Property value is undefined (but the property exists) in JS if the object was passed from .NET;value is never null (or missing) on an object from .NET. |
Option B: .NET null -> JS null
Alternatively, if .NET null values are marshalled as JS null, that has the following effects:
| Description | C# API | JS API | JS Notes |
|---|---|---|---|
| Method with optional param | void Method(T? p = null) | method(p: T | null): void | p is null in JS if a .NET caller omitted the parameter or supplied null;p is never undefined in JS when called by .NET. |
| Method with nullable param | void Method(T? p) | method(p?: T | null): void | p is null in JS if a .NET caller supplied null. |
| Method with nullable return | T? Method() | method(): T | null | Result is null in JS if .NET method returned null;result is never undefined when returned by .NET. |
| Nullable property | T? Property | property: T | null | Property value is null (and the property exists) in JS if the object was passed from .NET;value is never undefined (or missing) on an object from .NET. |
JavaScript null vs undefined practices
While null and undefined are often used interchangeably, the distinction can sometimes be important. Let's analyze some ways in which null and/or undefined values might be handled differently, and how common those practices are in JavaScript.
Detecting optional parameters to a JavaScript function
In JavaScript, there are several common ways to detect when an optional parameter was not supplied to a function:
function exampleFunction(optionalParameter?: any): voidCommon / best practice: Check if the value type is equal to
'undefined'.if (typeof optionalParameter === 'undefined')Somewhat common: Check if the value is strictly equal to
undefined.if (optionalParameter === undefined)Common / best practice in TS & ES2020: Use the "nullish coalescing operator", which handles both
nullandundefined:value = optionalParameter ?? defaultValueTraditional and still common (occasionally error-prone): Check if the value is falsy.
if (!optionalParameter)value = optionalParameter || defaultValueLess common: Check the length of the
argumentsobject. (Use of the specialargumentsobject is discouraged in modern JS, in favor of rest parameters.)if (arguments.length === 0)Uncommon: Check if the value is null with loose equality. It handles both
nullandundefinedbecausenull == undefined. (The loose equality operator is usually flagged by linters.)if (optionalParameter == null)
| A: null->undefined | B: null->null | |
|---|---|---|
| 1 | ✅ | ❌ |
| 2 | ✅ | ❌ |
| 3 | ✅ | ✅ |
| 4 | ✅ | ✅ |
| 5 | ❌ | ❌ |
| 6 | ✅ | ✅ |
Checking the return value of a function/method
A JavaScript function my return undefined, or null, when it yields no result. There is no strong consensus among the JS developer community about when to use either one; some developers may prefer one or the other while others may not think very hard about the distinction. There are a few ways the caller might check the return value:
function exampleFunction(): any- Traditional and still common (occasionally error-prone): Check if the result value is falsy.
if (!result) - Common: Check if the result value type is
'undefined'or value is strictly equal toundefined.if (typeof result === 'undefined')if (result === undefined) - Uncommon: Check if the result value is null with loose equality.
if (result == null)
| A: null->undefined | B: null->null | |
|---|---|---|
| 1 | ✅ | ✅ |
| 2 | ✅ | ❌ |
| 3 | ✅ | ✅ |
Detecting optional properties on a JavaScript object
In JavaScript, there are a few ways to detect when an optional property was not supplied with an object:
interface Example {
optionalProperty?: any;
}- Common: Use the
inoperator.if ('optionalProperty' in exampleObject) - Common: Use
hasOwnPropertyor the more modernhasOwnreplacement.if (!exampleObject.hasOwnProperty('optionalProperty'))if (!Object.hasOwn(exampleObject, 'optionalProperty')) - Traditional and still common (occasionally error-prone): Check if the property value is falsy.
if (!exampleObject.optionalProperty)if (!exampleObject['optionalProperty'])value = exampleObject.optionalProperty || defaultValue - Less common: Check if the property value type is
'undefined'or value is strictly equal toundefined.if (typeof exampleObject.optionalProperty === 'undefined')if (exampleObject.optionalProperty === undefined) - Less common: Use the nullish coalescing operator
value = exampleObject.optionalProperty ?? defaultValue - Uncommon: Check if the property value is null with loose equality.
if (exampleObject.optionalProperty == null)
| A: null->undefined | B: null->null | |
|---|---|---|
| 1 | ❌ | ❌ |
| 2 | ❌ | ❌ |
| 3 | ✅ | ✅ |
| 4 | ✅ | ❌ |
| 5 | ✅ | ✅ |
| 6 | ✅ | ✅ |
Note even when marshalling null to undefined, common checks that rely on the existince of properties can fail. And operations that enumerate the object properties may have differing behavior for missing properties vs ones with undefined value. For more on that subtle distinction, see TypeScript's --exactOptionalPropertyTypes option.
Checking for strict null equality
JavaScript code can specifically check if a parameter, return value, or property value is strictly equal to null:
if (value === null)| A: null->undefined | B: null->null |
|---|---|
| ❌ | ✅ |
More experienced JavaScript developers never write such code, since they are aware of the pervasiveness of undefined. But it can be easy for developers coming from other languages (like C# or Java) to write such code while assuming null works the same way, or merely from muscle memory.
JS APIs with semantic differences between undefined vs null
A JavaScript API could assign wholly different meanings to the two values, for instance using undefined to represent an uninitialized state and null to represent an intialized-but-cleared state. Since automatic marshalling of .NET null cannot support that distinction, calling such a JS API from .NET would require direct use of JSValue.Undefined and JSValue.Null (or perhaps a JS wrapper for the targeted API) to handle the disambiguation. But such an API design aspect would likely confuse many JavaScript developers as well, so it is not a common occurrence.
Design Choice
In the tables above, there are fewer ❌ marks in column A; this indicates that mapping .NET null to JS undefined is the better choice for default marshalling behavior.
There are a few rare cases in which the default may be problematic:
- Omitted optional function parameters, when the JS function body checks
arguments.length. - Omitted optional properties of an object, when the JS code checks whether the object has the property, or enumerates the object properties.
- A nullable (not optional) value where the JS code checks for strict null equality.
To handle these cases (and any other situations that might arise), we can add flags to the (planned) [JSMarshalAs] attribute to enable setting the null-value marshalling behavior of a specific .NET method parameter, return value, or property to one of three options:
undefined(default)null- omit - Exclude from the function arguments (if there are no non-omitted arguments after it), or exclude from the properties of the marshalled object.