How am I Misusing the Null-Coalescing Operator? Is This Evaluating "Null" Correctly

How am I misusing the null-coalescing operator? Is this evaluating null correctly?

This is Unity screwing with you.

If you do this:

MeshFilter GetMeshFilter()
{
MeshFilter temp = myGo.GetComponent<MeshFilter>();

if ( temp == null ) {
Debug.Log( " > Get Mesh Filter RETURNING NULL" );
return null;
}
return temp;
}

It works.

Why?

Because Unity has overridden Equals (and the == operator) on all Unity objects so that destroyed game objects and never existing objects are both "equal" to null (this was done in an attempt to make developer's lives easier). But a destroyed (or "missing") object is not literally null: its a wrapper object in the C# part of the engine pointing at a null object in the underlying C++ code. The null coalescing operator checks for literally null.

For example try this:

Start() {
GameObject gg = new GameObject(); //create a GO
DestroyImmediate(gg); //destroy it immediately
Debug.Log(gg == null); //prints true: it is definitely null!
GameObject go = gg ?? this.gameObject; //get a non-null object
Debug.Log(go); //prints null
}

This is also why you get a MissingReferenceException when you try to access Unity objects that are null, rather than a NullReferenceException: those objects aren't literally null, but only effectively null.


In general that's what the Object.bool operator is used for. Just prefer to use it instead of a null check:

public static T GetComponentOrAddIfMissing<T>(this GameObject self) where T : Component
{
T component = self.GetComponent<T>();

if(!component) component = self.AddComponent<T>();

return component;
}

How am I misusing the null-coalescing operator? Is this evaluating null correctly? I want it to show as 'Unknown' if it is null

Every member of the COALESCE function must be of the same data type.
You have mixed double precision type and text type. Solution :

SELECT
COALESCE((DATE_PART('year', death::date) - DATE_PART('year', birth::date)) :: text, 'Unknown') AS age
FROM emperors ORDER BY name ASC

Negate the null-coalescing operator

You could create an extension method which returns null when it tries to trim the value.

public String TrimIfNotNull(this string item)
{
if(String.IsNullOrEmpty(item))
return item;
else
return item.Trim();
}

Note you can't name it Trim because extension methods can't override instance methods.

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 as a ?? (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...

Null-condition and null-coalescing operator *vs.* plain boolean notation

Answer to your first question: Yes.

Short answer to your second question: None, you should choose based on which is more readable.

Answer to your third question: if you expect the whole expression to run "in one shot" and therefore not be subject to concurrency issues, then no, the null-coalescing operator does not guarantee that as explained in the answer of this Stackoverflow question.
In both your examples you would actually face the same concurrency challenges.

Long answer to your second question:

Looking in the Microsoft '??' doc, all is mentioned is the operator purpose and function:

The ?? operator is called the null-coalescing operator. It returns the left-hand operand if the operand is not null; otherwise it returns the right hand operand.

Hence, the null-coalescing operator makes you write cleaner code that would otherwise require you to write the operand in question twice (as in your 2nd example).

Usage of the null-coalescing operator is more related to utility than performance, as explained is the accepted answer of a similar Stackoverflow question. Indeed, both perform quite the same.

Interesting to notice, as part of the same answer, the null-coalescing operator seems to perform slightly faster, but the difference is so little that could be ignored.

Is the .NET Reflector unable to reflect over the null-coalescing operator correctly?

This is what my copy of Reflector makes of this method:

private Queue<int> EnsureQueue()
{
return (this._queue ?? (this._queue = new Queue<int>(10)));
}

Looks pretty darn good to me. Version 8.5.0.179, be sure to update yours.

C# type inference (var) assignment from '??' null-coalescing operator

It's not a runtime consideration.

The compile time type of a variable declared using var is the static type of its initializer. The static type of a ?? expression is the common type of the static type of both operands. But the static type of the second operand is the static type of y, which isn't known. Therefore the static type of the whole initializer is unknown, and deduction fails.

It's true that there exist types for which the initialization would be consistent, but they can't be found using the C# inference rules.



Related Topics



Leave a reply



Submit