Well, I haven’t updated this in a while…
Apologies. I don’t really have an excuse—I’ve just been distracted, which is obviously unacceptable. I swear updates to this blog will start occurring more often, starting now.
First order of business: that whole this == null
thing (see my last couple of posts). The question is already answered, but for those of you who are curious, let’s give an example.
class NullObject
{
public bool IsNull()
{
// Here is the line in question. Recall that I asked
// whether such a line would EVER evaluate as true.
return this == null;
}
public override bool Equals(object obj)
{
return obj == null || obj is NullObject;
}
public static bool operator ==(NullObject x, object y)
{
return y == null || y is NullObject;
}
public static bool operator !=(NullObject x, object y)
{
return y != null && !(y is NullObject);
}
}
Above I have defined a type called NullObject
that overloads the static ==
and !=
operators such that any variable of type NullObject
will always test positively for null
, regardless of whether it references an actual object or not.
See for yourself. Try out the following program:
public class Program
{
public static void Main()
{
var x = new NullObject();
// Remember: the IsNull method is internally checking
// this == null.
Console.WriteLine(x.IsNull());
}
}
Is such a type useful? Not to my knowledge. Just a random bit of trivia 😉
Next up: in my previous post I asked if any readers could think of a scenario where, rather than testing this
for nullity, it would be legal (in C#) to assign a value of null
to this
.
As I’m sure you could’ve guessed (otherwise why would I have even asked?), the answer is yes. This is due to the little-known (?) feature of C# whereby you can assign a value to this
for value types (i.e., struct
types).
So let’s give an example of a preliminary attempt to utilize this feature:
struct Null<T>
{
public T Value { get; set; }
public void Clear()
{
this = null;
}
}
Does the above code compile? No, actually, it doesn’t. The reason is that, since Null<T>
is a value type, it is not legal to assign null
to a Null<T>
variable.
You: So we’re at an impasse, then, aren’t we?
Me: Ha! You give up too easily.
There’s a little trick we can perform based on C#’s system for resolving the types in an expression involving implicit conversions. Actually, it has to do with C#’s algorithm for performing method overload resolution.
So before I reveal the trick itself, a quick primer:
Say I have this short program:
class Fruit { }
class Apple : Fruit { }
public class Program
{
public static void Main()
{
Fruit f = GetFruit();
Apple a = new Apple();
Eat(f);
Eat(a);
}
static Fruit GetFruit()
{
return new Apple();
}
static void Eat(Fruit fruit) { Console.WriteLine("Ate fruit"); }
static void Eat(Apple apple) { Console.WriteLine("Ate apple"); }
}
Do you know offhand what the above program will output? It can be tricky; honestly, a lot of developers guess wrongly (I know I have in the past, typically when I haven’t had enough coffee) that static methods exhibit the same kind of polymorphism as instance methods. Well, they don’t. The fact is that in the above program, where the compiler sees Eat(f);
it actually has to, you know, produce something. And what that something is depends on the type of f
.
You might protest, “but the type of f
is clearly Apple
!” True, but only in the stupendously oversimplified example above. Suppose we replaced the implementation of GetFruit
with this little morsel:
static Fruit GetFruit()
{
if (new Random().Next() % 2 == 0)
{
return new Apple();
}
return new Fruit();
}
What now? Where the compiler sees Eat(f);
, what does it output?
The reason the compiler does not produce some form of polymorphic behavior here is that the Eat
method is not a part of the Fruit
type. That is to say, the compiler cannot simply emit a callvirt
instruction, effectively saying, “Hey Fruit
, here’s a call to Eat
; you figure out what to do with it.” The Fruit
type knows absolutely nothing about any Eat
method. It cannot look it up in its vtable because it simply isn’t there; it’s defined elsewhere.
So the compiler produces code that will work based on a method that it knows for sure does exist. And that code outputs:
Ate fruit
Ate apple
Now, at this point we should be equipped to solve my little riddle. In particular, ask yourself, “Well, then, how does the compiler know how to compile Eat(a);
?”
Keep in mind that a
is actually declared to be of type Apple
. There’s no ambiguity because when the compiler has to pick which of two or more overloads to call, it goes with the best match. When multiple overloads involve paramters of types deriving from a common base, that best match is the one with the most specific type.
Which is more specific, Apple
or Fruit
? The answer is clear.
You: What does this have to do with implicit conversions?
Me: I’m getting to that! Geez, have some patience…
OK, so now let’s take a look at two implicit conversions, and then let’s try to figure out which one is called in a particular piece of code.
Here are the conversions:
class Food { }
class Hamburger : Food { }
class EdibleItem
{
public readonly int? Deliciousness;
private EdibleItem(int? deliciousness)
{
Deliciousness = deliciousness;
}
public static implicit operator EdibleItem(Food food)
{
return new EdibleItem(null);
}
public static implicit operator EdibleItem(Hamburger hamburger)
{
return new EdibleItem(int.MaxValue);
}
}
public class Program
{
public static void Main()
{
EdibleItem edibleItem = new Hamburger();
Console.WriteLine(edibleItem.Deliciousness);
}
}
What do you think that outputs?
If you guessed 2147483647, you’re absolutely correct. And the reason should be clear, based on what we’ve already learned about C#’s method overload resolution: that is, when faced with a choice between two methods whose signatures take parameters of type Food
and Hamburger
, the compiler will always choose the method whose signature takes a Hamburger
, since Hamburger
is the more specific type.
So here’s that trick I promised you (if you’ve been following me this whole time, you’ve probably actually come up with this on your own already):
sealed class NullReference
{
// No sense instantiating this type...
private NullReference() { }
}
struct Null<T>
{
public T Value { get; set; }
public void Clear()
{
this = null;
}
public static implicit operator Null<T>(NullReference obj)
{
return new Null<T>();
}
}
Given the above code, where the compiler sees this = null;
it effectively has two “overloads” to choose from:
- The language-defined unboxing conversion from
object
to Null<T>
- The user-defined implicit conversion from
NullReference
to Null<T>
Now, the truth is that the first choice above is a non-starter. It is not even really a choice at all, because unboxing conversions are explicit conversions, and the code in question is just this = null;
not this = (Null<T>)null;
(which still doesn’t compile—and even if it did, the correct choice would still be the second one).
This leaves us with the user-defined implicit conversion as the compiler’s only real choice. It exists, and it compiles; and therefore within the Null<T>
type we can write this = null;
and it’s perfectly legal.
So! I think that about wraps it up. Now since I’m kind of on a roll with the puzzling questions, here’s a bit of a brainteaser for you (don’t worry, it’s actually not that hard): what does the following code do?
sealed class NullReference
{
// Same as before.
private NullReference() { }
}
struct Null<T>
{
// Note the small change to this method.
public static implicit operator Null<T>(NullReference obj)
{
Console.WriteLine("Converting from NullReference to Null<{0}>...", typeof(T));
return null;
}
}
public class Program
{
// What will happen when this method is run?
public static void Main()
{
Null<int> x = null;
}
}
If you can figure out the answer without compiling and running it yourself, you deserve a cookie.