Unique ways to use the null coalescing operator
Well, first of all, it's much easier to chain than the standard ternary operator:
string anybody = parm1 ?? localDefault ?? globalDefault;
vs.
string anyboby = (parm1 != null) ? parm1
: ((localDefault != null) ? localDefault
: globalDefault);
It also works well if a null-possible object isn't a variable:
string anybody = Parameters["Name"]
?? Settings["Name"]
?? GlobalSetting["Name"];
vs.
string anybody = (Parameters["Name"] != null ? Parameters["Name"]
: (Settings["Name"] != null) ? Settings["Name"]
: GlobalSetting["Name"];
Is there a way to utilize the nullish coalescing operator (`??`) in object property destructuring?
I see two possible options:
Do the nullish coalescing property-by-property, or
Use a utility function
Property by property
Fairly plodding (I'm a plodding developer):
// Default `ExampleProps` here −−−−−−−−−−−−−−−vvvvv
export default function Example({ ExampleProps = {} }) {
// Then do the nullish coalescing per-item
const content = ExampleProps.content ?? "";
const title = ExampleProps.title ?? "Missing Title";
const date = ExampleProps.date ?? "";
const featuredImage = ExampleProps.featuredImage ?? {},
const author = ExampleProps.author ?? {},
const tags = ExampleProps.tags ?? [];
// ...
Utility function
Alternatively, use a utility function along these lines to convert null
values (both compile-time and runtime) to undefined
, so you can use destructuring defaults when destructuring the result. The type part is fairly straightforward:
type NullToUndefined<Type> = {
[key in keyof Type]: Exclude<Type[key], null>;
}
Then the utility function could be something like this:
function nullToUndefined<
SourceType extends object,
ResultType = NullToUndefined<SourceType>
>(object: SourceType) {
return Object.fromEntries(
Object.entries(object).map(([key, value]) => [key, value ?? undefined])
) as ResultType;
}
or like this (probably more efficient in runtime terms):
function nullToUndefined<
SourceType extends object,
ResultType = NullToUndefined<SourceType>
>(object: SourceType) {
const source = object as {[key: string]: any};
const result: {[key: string]: any} = {};
for (const key in object) {
if (Object.hasOwn(object, key)) {
result[key] = source[key] ?? undefined;
}
}
return result as ResultType;
}
Note that Object.hasOwn
is very new, but easily polyfilled. Or you could use Object.prototype.hasOwn.call(object, key)
instead.
(In both cases within nullToUndefined
I'm playing a bit fast and loose with type assertions. For a small utility function like that, I think that's a reasonable compromise provided the inputs and outputs are well-defined.)
Then:
export default function Example({ ExampleProps }) {
const {
content = "",
title = "Missing Title",
date = "",
featuredImage = {},
author = {},
tags = [],
} = nullToUndefined(ExampleProps || {});
// ^^^^^^^^^^^^^^^^−−−−−−−−−−−−−−−−−−^
// ...
Playground link
C# coalescing operator with 3 possible return values?
Absolutely - you could use ??
in the same way as any other binary operator, meaning that you could have any expression on its left and/or on its right.
For example, you could do something like this:
int? a = ...
int? b = ...
int? c = ...
int? d = ...
int? res = (condition_1 ? a : b) ?? (condition_2 ? c : d);
This expression will evaluate (condition_1 ? a : b)
first, check if it's null
, and then either use the non-null value as the result, or evaluate the right-hand side, and us it as the result.
You can also "chain" the null coalesce operators ??
, like this:
int? res =a ?? b ?? c ?? d;
Evaluation of this expression goes left to right.
The null-coalescing operator doesn't accepts different types
When you call NotFound()
, you are creating a NotFoundResult
. Your method has a return type of ActionResult<Round>
but NotFoundResult
doesn’t actually inherit from ActionResult<Round>
, so you cannot return the NotFoundResult
object directly.
When you do type return NotFound()
then what actually happens is that the compiler will use the implicit operator ActionResult<T> (ActionResult)
to transform the NotFoundResult
into a ActionResult<Round>
.
This works fine when you just return the value directly but it will not work when used within a ternary conditional or null-coalescing expression. Instead, you would have to do the conversion yourself:
public ActionResult<Round> Get(string id) =>
this._roundsService.Get(id) ?? new ActionResult<Round>(NotFound());
Because the constructor of ActionResult<T>
accepts any ActionResult
, you can just pass the NotFoundResult
to it to make sure that it gets converted properly.
Of course, you can also split this up again and have the compiler do the conversion for you:
public ActionResult<Round> Get(string id)
{
var result = this._roundsService.Get(id);
if (result != null)
return result;
return NotFound();
}
How the right associative of null coalescing operator behaves?
The spec is actually self-contradictory on this one.
Section 7.13 of the C# 4 spec states:
The null coalescing operator is right-associative, meaning that operations are grouped from right to left. For example, an expression of the form
a ?? b ?? c
is evaluated asa ?? (b ?? c)
.
On the other hand, as has been pointed out, 7.3.1 claims that:
Except for the assignment operators, all binary operators are left-associative
I entirely agree that for simple cases it doesn't matter how you do the grouping... but there may be cases where it really matters due to implicit type conversions doing interesting things if the operands have different types.
I'll consider it further, ping Mads and Eric, and add an erratum for the relevant section of C# in Depth (which inspired this question).
EDIT: Okay, I've now got an example where it does matter... and the null coalescing operator is definitely right-associative, at least in the MS C# 4 compiler. Code:
using System;
public struct Foo
{
public static implicit operator Bar(Foo input)
{
Console.WriteLine("Foo to Bar");
return new Bar();
}
public static implicit operator Baz(Foo input)
{
Console.WriteLine("Foo to Baz");
return new Baz();
}
}
public struct Bar
{
public static implicit operator Baz(Bar input)
{
Console.WriteLine("Bar to Baz");
return new Baz();
}
}
public struct Baz
{
}
class Test
{
static void Main()
{
Foo? x = new Foo();
Bar? y = new Bar();
Baz? z = new Baz();
Console.WriteLine("Unbracketed:");
Baz? a = x ?? y ?? z;
Console.WriteLine("Grouped to the left:");
Baz? b = (x ?? y) ?? z;
Console.WriteLine("Grouped to the right:");
Baz? c = x ?? (y ?? z);
}
}
Output:
Unbracketed:
Foo to Baz
Grouped to the left:
Foo to Bar
Foo to Bar
Bar to Baz
Grouped to the right:
Foo to Baz
In other words,
x ?? y ?? z
behaves the same as
x ?? (y ?? z)
but not the same as
(x ?? y) ?? z
I'm not currently sure why there are two conversions from Foo to Bar when using (x ?? y) ?? z
- I need to check that out more carefully...
EDIT: I now have another question to cover the double conversion...
Is there a way to implement and make use of a NOT null coalescing operator?
Mads Torgersen has publicly said that a null-propagating operator is under consideration for the next version of C# (but also emphasised that this doesn't mean it will be there). This would allow code like:
var value = someValue?.Method()?.AnotherMethod();
where the ?.
returns null
if the operand (on the left) is null
, else will evaluate the right hand side. I suspect that would get you a lot of the way here, especially if combined with (say) extension methods; for example:
DateTime? dtStartDate = strStartDate?.MyParse();
where:
static DateTime MyParse(this string value) {
return DateTime.ParseExact(value, "dd.MM.yyyy",
System.Globalization.CultureInfo.InvariantCulture
);
However! You could do the same thing right now just using extension methods:
DateTime? dtStartDate = strStartDate.MyParse();
static DateTime? MyParse(this string value) {
if(value == null) return null;
return DateTime.ParseExact(value, "dd.MM.yyyy",
System.Globalization.CultureInfo.InvariantCulture
);
Null-coalescing operators with operands of different types
Does anyone have a reason why this isn't allowed?
This is how the language is designed. What should it do? How'd you convert an int
, which is a 32-bit number, into a GUID
, which is a structure composed of several number portions, or vice-versa?
This code:
var ID3 = ID ?? ID2
translated into types means (pseudocode):
(inferred type_) = Guid? ?? int;
and the ??
operator naturally requires the types of its arguments to be compatible. The actual rules are fairly complex and can be found in the C# language specification here.
Anyway, the null-coalescing operator returns a single value and both its left and right arguments must be evaluated into a value of the same type. Hence, the result could be either a Guid
, or an int
. The (inferred type)
would then be this type. However, the ??
cannot convert the values of Guid
and int
operands into a value of such a single type.
Note: You could argue that the compiler could choose object
as the closes type compatible with both Guid
and int
and just box those values into an object. However, that would undermine the very sense of strong type checking. It would be just 'too relaxed'.
In your particular example, sticking with ??
seems artificial; a good old ?:
and a little bit more expressiveness in your code would do better and maybe even help readability:
var parameters = new Parameters()
{
new Parameter("id", FKGuid.HasValue ? ToTypeParameterExpects(FKGuid.Value) : ToTypeParameterExpects(id))
};
Obviously, the ToTypeParameterExpects
is a conversion to whatever type the Parameter
's constructor expects for the value.
C# Null Coalescing Operator Behaves Differently in Conjunction with OR Operator Between Null and Empty Collection
According to operators precedence, conditional OR operator ||
has higher priority than null-coalescing operator ??
.
According to null-coalescing operator specs:
The
??
operator doesn't evaluate its right-hand operand if the
left-hand operand evaluates to non-null.
So, in your code adding a parenthesis is needed to make the correct order of execution and ??
operator evaluation.
Without parenthesis this nullCollection?.Any()
returns null
, therefore ??
operator returns result of false || true
(because of priority, mentioned above), which is always true
.
emptyCollection?.Any()
result is not null
and returns false
, therefore right side false || true
isn't evaluated, compiler is smart enough to understand it.
Adding a parenthesis force the compiler to perform the expected execution of operators:
(nullCollection?.Any() ?? false)
returnsfalse
because left-side
operand isnull
and right side operand is executed.(emptyCollection?.Any() ?? false)
also returnsfalse
, left-side
operand isn'tnull
and right side isn't executed.
Logical OR for false || true
returns true
in both cases and you'll see a desired behavior
Related Topics
How to Tell Fluent Nhibernate Not to Map a Class Property
How to Find the Current Executable Filename
Best Practice for Exception Handling in a Windows Forms Application
How to Access Elements of a Jarray (Or Iterate Over Them)
Dynamic Button Creation & Placing Them in a Predefined Order Using C#
Initializing a Generic Variable from a C# Type Variable
How to Highlight Wrapped Text in a Control Using the Graphics
Curious Null-Coalescing Operator Custom Implicit Conversion Behaviour
Define a Generic That Implements the + Operator
Httpcontext.Current Is Null When Unit Test
How to Override with Derived Types
Is Graphics.Drawimage Too Slow for Bigger Images
Insert Datetime Value in SQL Database with C#
Proper Localization of a Winforms Application
I Have to Access/Commit/Update Svn Repository in Wpf Application Using Svn API or Libraries