I’ve compiled a few C# brain teasers that you can explore interactively.

They are a tougher than the ones I posted previously but are only limited to the C# 2.0 feature set, to keep things simple.

Try to guess the answer to each question and then hit Go to evaluate the code.

The explanations for the answers are at the bottom of this post.

1)

//Can you guess what the output is? for(int i = 0; i < 10; i++) { Console.Write(i + ' '); }

Explanation



2)

//Class declarations class Base { } class A : Base { public int Field; } class B : Base { public int Field = 0; public static implicit operator A(B source) { return new A { Field = source.Field }; } } // Is there anything wrong with the assignment below? A obj = new B(); // Do any of these four statements compile? /* 1. */ object[] oArr = new string[5]; /* 2. */ A[] aArr = new B[5]; /* 3. */ object[] oArr2 = new int[5]; /* 4. */ long[] dArr = new int[5];

Explanation



3)

//Can you guess what the output is? struct Counter { int counter; public override string ToString() { return counter++.ToString(); } } Counter c = new Counter(); Console.WriteLine(c); Object o = c; Object r = o; Console.WriteLine(c); Console.WriteLine(r); Console.WriteLine(o); Console.WriteLine(o); Console.WriteLine(r);

Explanation



4)

//What is wrong with this code? class Outer { static int sField = 0; public class Nested { public virtual int GetFieldValue() { return sField; } } } class SubClass : Outer.Nested { public override int GetFieldValue() { return sField + 5; } }

Explanation



5)

//What is the length of the strings? string s1 = "\U0010FADE"; string s2 = "\U0000FADE"; Console.WriteLine(s1.Length); Console.WriteLine(s2.Length);

Explanation



6)

//What is the output? i.e. the lengths of the arrays? int[] singleDimension = {1,2,3,4}; int[,] multiDimension = {{1,2},{3,4}}; int[][] jagged = { new int[] { 1, 2 }, new int[] { 3, 4 } }; Console.WriteLine(singleDimension.Length); Console.WriteLine(multiDimension.Length); Console.WriteLine(jagged.Length);

Explanation



7)

//Class declarations class Base { protected class C { public C() { Console.WriteLine("Base.C()"); } } } class A : Base { public A() { Base.C inheritedC = new Base.C(); C myC = new C(); } new class C { public C() { Console.WriteLine("A.C()"); } } } class B : A { public B() { C implicitC = new C(); Base.C baseC = new Base.C(); } } //What is outputted when a new B object is created? B b = new B();

Explanation



8)

// What is wrong with this code? class B { private static void M() { Console.WriteLine("B.M"); } public static void M(int i) { Console.WriteLine("B.M(int)"); } } class E { static void M() { Console.WriteLine("E.M"); } static void M(int i) { Console.WriteLine("E.M(int)"); } class C : B { public static void Main() { M(); } } }

Explanation



9)

//Which field initializer statement(s) in the class will not compile? class A { bool a = false; object b = new A(); bool c = a; object d = this; int j = i + 1; static int i = 4; }

Explanation



10)

//What is wrong with this code? class Generator { readonly Random rnd; public Generator(ref Random RandomGenerator) { GetRandomGenerator(out rnd); RandomGenerator = rnd; } public void Reset(ref Random RandomGenerator) { GetRandomGenerator(out rnd); RandomGenerator = rnd; } private void GetRandomGenerator(out Random RandomGenerator) { RandomGenerator = new Random(); } }

Explanation



11)

// Why won't the code below compile? class A { private int i; public int P { get { return i; } set { i = value; } } public int get_P() { return i; } public void set_P(int i) { this.i = i; } }

Explanation



12)

//Why won't the code below compile? class A { int Item = 0; public int this[params int[] arr] { get { return Item; } } }

Explanation



13)

//What is the output? float number = 23, zero = 0; try { Console.WriteLine(number / zero); } catch (DivideByZeroException) { Console.WriteLine("Division By Zero"); }

Explanation



14)

//What is the output? //Checked context float fMax = float.MaxValue; int iMax = int.MaxValue; decimal decMax = decimal.MaxValue; checked { try { Console.WriteLine(fMax += 10); } catch { Console.WriteLine("Float Overflow"); } try { Console.WriteLine(iMax += 10); } catch { Console.WriteLine("Integer Overflow"); } try { Console.WriteLine(decMax += 10); } catch { Console.WriteLine("Decimal Overflow"); } } //Unchecked context fMax = float.MaxValue; iMax = int.MaxValue; decMax = decimal.MaxValue; unchecked { try { Console.WriteLine(fMax += 10); } catch { Console.WriteLine("Float Overflow"); } try { Console.WriteLine(iMax += 10); } catch { Console.WriteLine("Integer Overflow"); } try { Console.WriteLine(decMax += 10); } catch { Console.WriteLine("Decimal Overflow"); } }

Explanation



15)

// Class declaration class B { public B() { M1(); } public virtual void M1() { Console.WriteLine("B.M1"); } } class C : B { int f; public C() { f = 7; } public override void M1() { Console.Write("C.M2 : " + f); } } // What is outputted when a new C object is created? new C();

Explanation



16)

// Type declarations class MyClass { private int PrivA; public int PubB; public MyClass(int a, int b) { this.PrivA = a; this.PubB = b; } } struct MyStruct { private int PrivA; public int PubB; public MyStruct(int a, int b) { this.PrivA = a; this.PubB = b; } } // What is outputted? MyClass c1 = new MyClass(50, 99); MyClass c2 = new MyClass(70, 99); MyClass c3 = new MyClass(50, 99); MyStruct s1 = new MyStruct(50, 99); MyStruct s2 = new MyStruct(70, 99); MyStruct s3 = new MyStruct(50, 99); Console.WriteLine(c1.Equals(c2)); Console.WriteLine(c1.Equals(c3)); Console.WriteLine(s1.Equals(s2)); Console.WriteLine(s1.Equals(s3));

Explanation

Explanations:

A1.

The output is 32333435363738394041.

A char is a numeric value that is expressed as a character so 1 + ' ' is really 1 + 32.

A2.

The A obj = new B() assignment is legal since an implicit conversion exists between classes A and B.

The first statement will compile because the String class derives from the Object class and so qualifies as an implicit reference conversion.

The second statement will not compile because even though there is a user defined implicit conversion, only standard implicit reference conversions are considered for arrays.

The third statement will not compile because even though Int32 derives from the Object class, it is a value type and cannot partake in an implicit reference conversion.

The fourth statement will not compile because even though the Int32 type is implicitly convertible to the Int64 (long) type, they are both value types and arrays can only convert from reference types that qualify for an implicit reference conversion.

A3.

The output is:

0

0

0

1

2

3

How’s that? This is a case of boxing and unboxing turned on its head.

When the ToString() method override is called, struct c is automatically boxed to an object type to facilitate the call. This stores a copy of c on the heap and calls ToString() on that copy.

Therefore the counter field on the actual struct c never changes. The counter field for struct c will always be 0, regardless of how many times ToString() is called.

Object o is a boxed copy of struct c and object r references the same boxed copy. When ToString() is called on either o or r, the boxed copy is accessed directly and the counter is incremented.

Thus calls to o.ToString() and r.ToString() increment the same referenced counter structure.

This shows that contrary to popular opinion, it is possible, even trivial, to modify a boxed value.

A4.

The static Outer.sField private field is accessible to class Nested but not accessible to class SubClass because the scope of a static member includes nested classes, but it excludes subclasses of the nested class.





A5.

The output is:

2

1



The \Udddddddd escape code is used for encoding UTF-16 unicode characters (also referred to as codepoints).

System.Char structures store 16 bit values (0x0000 through 0xFFFF) and so a single System.Char is unable to store unicode characters that fall within the 0x10000 and 0x10FFFF range.

To accommodate these characters, multiple System.Char structures are used.

This is the case for string s1, where two chars are used to store the unicode character.

In string s2, only one char stores the unicode character.



String.Length returns the number of System.Char structures in the string, not the number of unicode characters.

A6.

The output is:

4

4

2



The Length property of arrays returns the total number of elements in single and multi-dimensional arrays.

However, it returns only the number of elements in the first dimension of a jagged array.

A7.

The output is:

Base.C()

A.C()

Base.C()

Base.C()



Nested class Base.C is marked with a protected modifier and so will be inherited by classes that derive from Base. This is similar to the way protected fields are inherited, except in this case it is the access to Base.C that is being inherited.

Class A is derived from Base and so inherits access to Base.C , but also defines a nested class called C, which hides Base.C.

Base.C is still accessible from within class A but must be referred to explicitly by Base.C.

Classes that derive from class A will have access to both Base.C and A.C. However, it's important to note that external access to A.C points to the inherited Base.C because the new class A.C is implicitly private and is only visible and accessible to code within class A.

Class B is derived from class A. It inherits access to Base.C and the externally visible A.C (which is actually Base.C), so variables implicitC and baseC refer to objects constructed from the same class.



If the new class A.C's access modifier is changed to protected i.e. protected new class C , the code will output

Base.C()

A.C()

A.C()

Base.C()

because B will now inherit access to the true class A.C. In fact, since class A.C is closer to class B than class Base.C in the inheritance hierarchy, A.C can be implicitly referred to as C, and so the variable implicitC now maps to an object constructed from class A.C whereas variable baseC will refer to an object constructed from class Base.C.

A8.

The call to M() in the Main method is not allowed. Even though there is an accessible static M() method in the enclosing class E, the call refers to the inaccessible B.M() method because inherited members of C are preferred over members of enclosing classes.

There is an accessible inherited method M(int i), so all calls to methods named M (of any signature) from class C will be mapped to the M methods in the base class B.



It's not possible to call an overload of a method in the base class and call another overload in the enclosing class.

The compiler will choose to call either all overloads in the base class (preferably) or all overloads in the enclosing class.

If you comment out the B.M(int i) method overload, then the static M method in the enclosing class is called.

A9.

These two statements will not compile:

bool c = a; //CS0236 A field initializer cannot reference the nonstatic field, method, or property 'field'

object d = this; //CS0027 Keyword this is not available in the current context

Outside a method, instance fields cannot be used to initialize other instance fields.

The this keyword is unavailable outside a method, property or constructor.

Interestingly, the field A.b that initializes a brand new object A is allowed.

A10.

The following line will not compile:

GetRandomGenerator(out rnd); //CS0192 A readonly field cannot be passed ref or out (except in a constructor)

A readonly field cannot be passed as a ref or out parameter (except in a constructor).

Constructors can accept ref and out parameters.

A11.

Properties are compiled into get_{Property_Name} and set_{Property_Name} methods.

The methods get_P and set_P will conflict with the generated property methods.

A12.

Indexers are compiled into a property named Item and methods named get_Item and set_Item.

The field Item conflicts with the generated property name.

It’s okay to have a params modifier in an indexer.

A13.

The output is:

Infinity



Only integral (int, long, short, byte, uint, ulong, ushort, sbyte, char) and the decimal types throw a DivisionByZero exception when divided by zero.

Float and Double types will return special IEEE754 values such as Infinity or NaN.

A14.

The output is:

3.402823E+38

Integer Overflow

Decimal Overflow

3.402823E+38

-2147483639

Decimal Overflow



Overflows in integral (int, long, short, byte, uint, ulong, ushort, sbyte, char) types wrap around in an unchecked context. In a checked context they throw an overflow exception.

Overflows in floating point (float and double) types always wrap around and never throw an overflow exception in any context. They can also produce special IEEE754 values such as Infinity or NaN as results.

Overflows in the decimal type always throw an overflow exception in any context.

A15.

The output is:

C.M2 : 0



When C's constructor is called, It implicitly calls the base constructor before the field f assignment.

B's constructor calls the M1 method, a virtual-instance method, which resolves to method C.M1.

At the point when C.M1 is called from B's constructor, the value of field f has not been assigned yet and is the default value of zero.

A16.

The output is:

False

False

False

True

By default the Equals method compares references in reference types. i.e returns true if both variables refer to the same object.

In value types, it will compare the types of the variables. If they are not the same, it returns false.

If they are the same type, it will compare the value of each field (including private ones).