Here's a collection of C# quirks that aren't actually quirks just implementation side effects of the compiler and the specification.
Type Inference and the Conditional Operator
The conditional operator (?:) returns one of two values depending on the value of the Boolean expression. Sounds straight forward enough but try and figure out why a compile error is thrown on line 4.
1: int i = 9;
2: int j = 8;
3: int max = i > j ? i : j; // works fine
4: int? nullableMax = i > j ? i : null; // compiler error
The answer lies in Section 7.13 of the C# 3.0 specification which states on page 200-201:
The second and third operands of the ?: operator control the type of the conditional expression. Let X and Y be the types of the second and third operands. Then,
• If X and Y are the same type, then this is the type of the conditional expression.
• Otherwise, if an implicit conversion (§6.1) exists from X to Y, but not from Y to X, then Y is the type of the conditional expression.
• Otherwise, if an implicit conversion (§6.1) exists from Y to X, but not from X to Y, then X is the type of the conditional expression.
• Otherwise, no expression type can be determined, and a compile-time error occurs.
In other words, the compiler sees an integer, i, and a null, and since there's no conversion between the two it throws a "no implicit conversion" error.
By default, integer overflow is silent for runtime-evaluated items, but active for compile-time constants
This catches many programmers out until it has bitten them. I'd prefer to see it ON by default. If the programmer knows exactly what they are doing and wants to suppress it they can go and turn overflow off.
int a = Int32.MinValue;
a = a - 1999; // no exception
int b = Int32.MinValue - 1999; // compile-time error
Yes, you can go in to the Advanced Build Settings in Visual Studio and force the use of the /checked+ command-line switch, but this will only happen after the programmer has been bitten by this issue. A lesser evil is to let the programmer suffer the performance overhead of, perhaps unwanted, overflow checking rather than unusual program behaviour.
Arithmetic operators are not defined for bytes and shorts
In the code shown below we are trying to add two shorts, but since the addition operator (+) is not defined for shorts, the operands are promoted to 32-bit integers and then the addition takes place. This upcast is implicit since it is a widening conversion however the addition operation produces an System.Int32 result which now needs to be explicitly downcast to a short because it is not a widening conversion. That leaves many programmers scratching their head!
short x = 1;
short y = 1;
short z = x + y; // compile-time error
short zz = (short)(x + y); // no error
Division By Zero on Floating Points
Take a look at the code below. Will a DivisionByZero exception by thrown on line 3?
1: double i = 7.5;
2: double j = 0;
3: Console.WriteLine( i/j ); // will this throw at run-time?
The answer is NO is won't. If the data types were integral it certainly would, by dividing a floating point number by zero results in infinity and no exception!
Bankers Rounding By Default
This is truly bizarre. The Math.Round() function in .NET doesn't really follow the Principle of Least Astonishment. Check this out...
static void Main()
Console.WriteLine(Math.Round(-2.5)); // -2
Console.WriteLine(Math.Round(-1.5)); // -2
Console.WriteLine(Math.Round(-0.5)); // 0
Console.WriteLine(Math.Round(0.0)); // 0
Console.WriteLine(Math.Round(0.5)); // 0
Console.WriteLine(Math.Round(1.5)); // 2
Console.WriteLine(Math.Round(2.5)); // 2
Math.Round() uses Bankers' Rounding which is not what most people would expect. To get what you'd think it would do by default you need to do this:
Math.Round(0.5, MidpointRounding.AwayFromZero )
Method Overload Resolution
I originally came across this on Jon Skeet's website, who apparently got it from Ayende. Try and figure out which method is called and what is printed to the console...
1: static void Main()
6: static void Foo(object x)
11: static void Foo<T>(params T x)
13: Console.WriteLine("params T");
It hits the generic Foo(params T x) function but why? According to Section 7.4.3 of the C# 3.0 Specification overload resolution works in 2 parts: the first part identifies the set of applicable function members and the second part finds the "better function" match. In this case there are 2 applicable functions with slightly different parameter lists: object x, and params string x. Because the second candidate has a parameter list the compiler considers both the "normal form" and the "expanded form". To quote the spec...
"For a function member that includes a parameter array, if the function member is applicable ..., it is said to be applicable in its normal form. If a function member that includes a parameter array is not applicable in its normal form, the function member may instead be applicable in its expanded form. The expanded form is constructed by replacing the parameter array in the function member declaration with zero or more value parameters of the element type of the parameter array such that the number of arguments in the argument list A matches the total number of parameters "
Section 22.214.171.124 of the specification defines the logic for the "better function" and to cut a long story short, it picks (string x) - the expanded form of the parameter array argument list - to be a better argument list match than (object x).
More Method Overloading Intrigue
Look a the code below (shamelessly stolen from Jon Skeet's website) and try and figure out what is printed to the console.
1: class Base
3: public virtual void DoSomething(int x)
9: class Derived : Base
11: public override void DoSomething(int x)
16: public void DoSomething(object o)
22: class Test
24: static void Main()
26: Derived d = new Derived();
27: int i = 10;
Jon Skeet explained it best..."Derived.DoSomething(object) is printed - when choosing an overload, if there are any compatible methods declared in a derived class, all signatures declared in the base class are ignored - even if they're overridden in the same derived class!"
Nullable Type Boxing
Saw this one on StackOverflow - original creator was Marc Gravell. Try and guess why a NullReference exception is thrown...
1: static void Foo<T>() where T : new()
3: T t = new T();
4: Console.WriteLine(t.ToString()); // works fine
5: Console.WriteLine(t.GetHashCode()); // works fine
6: Console.WriteLine(t.Equals(t)); // works fine
8: // so it looks like an object and smells like an object...
10: // but this throws a NullReferenceException...
14: static void Main()
Answer: 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. Interestingly an empty Nullable boxes to null, not a box that contains an empty Nullable.