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
null
andundefined
primitive 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
JSValue
and its distinctJSValue.Null
andJSValue.Undefined
values.
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): void
Common / 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
null
andundefined
:value = optionalParameter ?? defaultValue
Traditional and still common (occasionally error-prone): Check if the value is falsy.
if (!optionalParameter)
value = optionalParameter || defaultValue
Less common: Check the length of the
arguments
object. (Use of the specialarguments
object 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
null
andundefined
becausenull == 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
in
operator.if ('optionalProperty' in exampleObject)
- Common: Use
hasOwnProperty
or the more modernhasOwn
replacement.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.