C
C#2y ago
Ruttie

✅ `??=` and `??`

This question may not fit here because it is not a question about C# but more about why it is implemented in such a way, but I'll ask anyways: Why is item ??= item2 implemented as
if ((object)item == null)
{
item = item2;
}
if ((object)item == null)
{
item = item2;
}
instead of
if (item == null)
{
item = item2;
}
if (item == null)
{
item = item2;
}
? The cast makes it so if the == operator is changed by the class, it will not use said operator, instead using the default == operator of object. This makes it so that in unity for example, where the == operator is changed by all unity objects, the ??= operator cannot be used. My other question is, does the ?? operator also cast to object before null checking, or does it use something else completely, like pattern matching? I've used https://sharplab.io to see the lowering of ??, but it doesn't appear to be lowered.
37 Replies
ero
ero2y ago
how are you testing this? i don't see a cast being done
Ruttie
RuttieOP2y ago
The exact code I used to test this is
public static partial class Program
{
private static ClassThingie? item;
public static unsafe void Main(string[] args)
{
ClassThingie inst = null;
if (inst == null) ;
inst ??= item ?? GetThingie();
}

public static ClassThingie GetThingie() =>
new();
}

public class ClassThingie
{
public static bool operator ==(ClassThingie left, ClassThingie right)
{
Console.WriteLine($"{nameof(ClassThingie)} equality operator called.");
return true;
}

public static bool operator !=(ClassThingie left, ClassThingie right)
{
Console.WriteLine($"{nameof(ClassThingie)} inequality operator called.");
return true;
}

public override bool Equals(object? obj)
{
return this == (ClassThingie)obj!;
}
}
public static partial class Program
{
private static ClassThingie? item;
public static unsafe void Main(string[] args)
{
ClassThingie inst = null;
if (inst == null) ;
inst ??= item ?? GetThingie();
}

public static ClassThingie GetThingie() =>
new();
}

public class ClassThingie
{
public static bool operator ==(ClassThingie left, ClassThingie right)
{
Console.WriteLine($"{nameof(ClassThingie)} equality operator called.");
return true;
}

public static bool operator !=(ClassThingie left, ClassThingie right)
{
Console.WriteLine($"{nameof(ClassThingie)} inequality operator called.");
return true;
}

public override bool Equals(object? obj)
{
return this == (ClassThingie)obj!;
}
}
I used if (inst == null) ; to check if there is maybe a problem with my code, but the result is that it is only logged once (that once being this check).
phaseshift
phaseshift2y ago
What's the point of that if?
Ruttie
RuttieOP2y ago
as for sharplab.io, this is the result I get
Ruttie
RuttieOP2y ago
https://discord.com/channels/143867839282020352/1053717640025210892/1053718999537557574 I would expect this code to output ClassThingie equality operator called. thrice, or at least twice if ?? uses pattern matching, but it's only printed once
ero
ero2y ago
i can't put it into words, but this is pretty much bogus ?? checks if (thing is null) { } there's nothing else to it all it checks is whether what's passed exists at all there is no "equality" being checked
Ruttie
RuttieOP2y ago
while that's the answer of my second question, it doesn't explain why ??= does a cast
ero
ero2y ago
exactly so it doesn't call the equality operator of the class because there is no. equality. being checked. nothing is "equal" to null can't be equal to something that doesn't exist, doesn't even have a concept of a type
Ruttie
RuttieOP2y ago
unity seems to disagree then?
ero
ero2y ago
because they're fucking braindead (no offense of course) unity's implementation of almost everything is a complete mistake they took the concept of "null equals false" from c++ (which is where their engine source code resides), and incorrectly implemented that into their c# API which is absolutely asinine a null value cannot equate to false
Ruttie
RuttieOP2y ago
Okay, so the simple answer is just, don't use ??= and ?? with unity.
ero
ero2y ago
unfortunately
Ruttie
RuttieOP2y ago
gotcha thanks for your help!
ero
ero2y ago
the reason "null equals false" in lower level languages like C, C++, Python (unfortunately) is because they're comparing the pointers and a null pointer evaluates to 0 and 0 evaluates to false a non-zero value evaluates to true
Ruttie
RuttieOP2y ago
yeah
ero
ero2y ago
so yes, unity completely fucked up when making an implicit bool operator for UnityObject that equates to false when an object is null
Anton
Anton2y ago
you should be able to use the implicit bool conversions in Unity, or just check for null obj ? whenNotNull : whenNull this should work
333fred
333fred2y ago
It doesn't
D.Mentia
D.Mentia2y ago
worth adding that item is null also does that same sort of object cast, ignoring any == overloads, because sometimes you want to make sure not to use overloaded operators for null checks
333fred
333fred2y ago
It does the same thing as ?? There is no cast involved
ero
ero2y ago
there is though?
333fred
333fred2y ago
No there isn't
D.Mentia
D.Mentia2y ago
IDK if there is one but it's as if there is one anyway, just pointing out that it's common to want to skip the overloads on null checks
333fred
333fred2y ago
That may be how the decompiler chooses to represent it when going back to C# But it's not what happens in il
D.Mentia
D.Mentia2y ago
https://ericlippert.com/2013/05/30/what-the-meaning-of-is-is/ here's a long post about why is null is different from == null which may explain why ?? works like the former
ericlippert
Fabulous adventures in coding
What the meaning of is is
Today a follow-up to my 2010 article about the meaning of the is operator. Presented as a dialog, as is my wont! I’ve noticed that the is operator is inconsistent in C#. Check this out: strin…
333fred
333fred2y ago
And yes, unity does bad things like overloading == so null is weird for them I get why they did it, but it didn't help their users wrt this type of thing
ero
ero2y ago
a bit misleading by sharplab then, or by decompilers, or whichever
MODiX
MODiX2y ago
Ero#1111
sharplab.io (click here)
Foo foo = null;
Foo bar = new();
foo ??= bar ?? new();
record Foo();
Foo foo = null;
Foo bar = new();
foo ??= bar ?? new();
record Foo();
React with ❌ to remove this embed.
ero
ero2y ago
Foo foo = new Foo();
if ((object)null == null && (object)foo == null)
{
new Foo();
}
Foo foo = new Foo();
if ((object)null == null && (object)foo == null)
{
new Foo();
}
333fred
333fred2y ago
It's a reasonable way to represent in C# But when you look at the il, you'll see no casts Think about it this way: reference conversions (such upcasting a class) mean nothing. They tell the compiler things, but absolutely nothing needs to happen at runtime The only time they matter is when they can't be proved at compile time, such as downcasts
ero
ero2y ago
so then, what is the reason for the operator not to be executed? actually, i guess a null check is indeed entirely different
333fred
333fred2y ago
The == operator? That's not how any of the null-conditional operators are defined
ero
ero2y ago
isn't it just like a cmp against 0 or something
333fred
333fred2y ago
They check for null, and only null
ero
ero2y ago
mh, yeah this, basically
333fred
333fred2y ago
Correct
Accord
Accord2y ago
Was this issue resolved? If so, run /close - otherwise I will mark this as stale and this post will be archived until there is new activity.
Want results from more Discord servers?
Add your server