What's the strangest corner case you've seen in C# or .NET? [closed]
I collect a few corner cases and brain teasers and would always like to hear more. The page only really covers C# language bits and bobs, but I also find core .NET things interesting too. For example, here's one which isn't on the page, but which I find incredible:
string x = new string(new char[0]);
string y = new string(new char[0]);
Console.WriteLine(object.ReferenceEquals(x, y));
I'd expect that to print False - after all, "new" (with a reference type) always creates a new object, doesn't it? The specs for both C# and the CLI indicate that it should. Well, not in this particular case. It prints True, and has done on every version of the framework I've tested it with. (I haven't tried it on Mono, admittedly...)
Just to be clear, this is only an example of the kind of thing I'm looking for - I wasn't particularly looking for discussion/explanation of this oddity. (It's not the same as normal string interning; in particular, string interning doesn't normally happen when a constructor is called.) I was really asking for similar odd behaviour.
Any other gems lurking out there?
Solution 1:
I think I showed you this one before, but I like the fun here - this took some debugging to track down! (the original code was obviously more complex and subtle...)
static void Foo<T>() where T : new()
{
T t = new T();
Console.WriteLine(t.ToString()); // works fine
Console.WriteLine(t.GetHashCode()); // works fine
Console.WriteLine(t.Equals(t)); // works fine
// so it looks like an object and smells like an object...
// but this throws a NullReferenceException...
Console.WriteLine(t.GetType());
}
So what was T...
Answer: any Nullable<T>
- such as int?
. All the methods are overridden, except GetType() which can't be; so it is cast (boxed) to object (and hence to null) to call object.GetType()... which calls on null ;-p
Update: the plot thickens... Ayende Rahien threw down a similar challenge on his blog, but with a where T : class, new()
:
private static void Main() {
CanThisHappen<MyFunnyType>();
}
public static void CanThisHappen<T>() where T : class, new() {
var instance = new T(); // new() on a ref-type; should be non-null, then
Debug.Assert(instance != null, "How did we break the CLR?");
}
But it can be defeated! Using the same indirection used by things like remoting; warning - the following is pure evil:
class MyFunnyProxyAttribute : ProxyAttribute {
public override MarshalByRefObject CreateInstance(Type serverType) {
return null;
}
}
[MyFunnyProxy]
class MyFunnyType : ContextBoundObject { }
With this in place, the new()
call is redirected to the proxy (MyFunnyProxyAttribute
), which returns null
. Now go and wash your eyes!
Solution 2:
Bankers' Rounding.
This one is not so much a compiler bug or malfunction, but certainly a strange corner case...
The .Net Framework employs a scheme or rounding known as Banker's Rounding.
In Bankers' Rounding the 0.5 numbers are rounded to the nearest even number, so
Math.Round(-0.5) == 0
Math.Round(0.5) == 0
Math.Round(1.5) == 2
Math.Round(2.5) == 2
etc...
This can lead to some unexpected bugs in financial calculations based on the more well known Round-Half-Up rounding.
This is also true of Visual Basic.
Solution 3:
What will this function do if called as Rec(0)
(not under the debugger)?
static void Rec(int i)
{
Console.WriteLine(i);
if (i < int.MaxValue)
{
Rec(i + 1);
}
}
Answer:
- On 32-bit JIT it should result in a StackOverflowException
- On 64-bit JIT it should print all the numbers to int.MaxValue
This is because the 64-bit JIT compiler applies tail call optimisation, whereas the 32-bit JIT does not.
Unfortunately I haven't got a 64-bit machine to hand to verify this, but the method does meet all the conditions for tail-call optimisation. If anybody does have one I'd be interested to see if it's true.