Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the strangest corner case you've seen in C# or .NET? [closed]

Tags:

c#

.net

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!


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.


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.


Assign This!


This is one that I like to ask at parties (which is probably why I don't get invited anymore):

Can you make the following piece of code compile?

    public void Foo()
    {
        this = new Teaser();
    }

An easy cheat could be:

string cheat = @"
    public void Foo()
    {
        this = new Teaser();
    }
";

But the real solution is this:

public struct Teaser
{
    public void Foo()
    {
        this = new Teaser();
    }
}

So it's a little know fact that value types (structs) can reassign their this variable.


Few years ago, when working on loyality program, we had an issue with the amount of points given to customers. The issue was related to casting/converting double to int.

In code below:

double d = 13.6;

int i1 = Convert.ToInt32(d);
int i2 = (int)d;

does i1 == i2 ?

It turns out that i1 != i2. Because of different rounding policies in Convert and cast operator the actual values are:

i1 == 14
i2 == 13

It's always better to call Math.Ceiling() or Math.Floor() (or Math.Round with MidpointRounding that meets our requirements)

int i1 = Convert.ToInt32( Math.Ceiling(d) );
int i2 = (int) Math.Ceiling(d);

They should have made 0 an integer even when there's an enum function overload.

I knew C# core team rationale for mapping 0 to enum, but still, it is not as orthogonal as it should be. Example from Npgsql.

Test example:

namespace Craft
{
    enum Symbol { Alpha = 1, Beta = 2, Gamma = 3, Delta = 4 };


   class Mate
    {
        static void Main(string[] args)
        {

            JustTest(Symbol.Alpha); // enum
            JustTest(0); // why enum
            JustTest((int)0); // why still enum

            int i = 0;

            JustTest(Convert.ToInt32(0)); // have to use Convert.ToInt32 to convince the compiler to make the call site use the object version

            JustTest(i); // it's ok from down here and below
            JustTest(1);
            JustTest("string");
            JustTest(Guid.NewGuid());
            JustTest(new DataTable());

            Console.ReadLine();
        }

        static void JustTest(Symbol a)
        {
            Console.WriteLine("Enum");
        }

        static void JustTest(object o)
        {
            Console.WriteLine("Object");
        }
    }
}