Inheritance and Generic Type Setting

Question

Can someone explain to me why the below code outputs what it does? Why is T a String in the first one, not an Int32, and why is it the opposite case in the next output?

This puzzle is from an interview with Eric Lippert

When I look through the code, I really have no idea if it's going to be an Int32 or a String:

public class A<T>
    {
        public class B : A<int>
        {
            public void M() { System.Console.WriteLine(typeof(T)); }
            public class C : B { }
        }
    }

    public class P
    {
        public static void Main()
        {            
            (new A<string>.B()).M(); //Outputs System.String
            (new A<string>.B.C()).M(); //Outputs System.Int32
            Console.Read();
        }
    }

Show source
| generics   | c#   | inheritance   2016-12-25 10:12 5 Answers

Answers to Inheritance and Generic Type Setting ( 5 )

  1. 2016-12-25 10:12

    Method M inside B prints typeof(T) of A<T>, A is parent class of B.

    So irrespective of whether B is derived from whatever, M prints typeof(T) that is String.

    So A<T>.B.M prints nearest A's T.

    So A<string>.B.M will print string

    Now, let us expand expression A<string>.B.C, which is equivalent to A<string>.B.A<int>.B (since C is A<int>.B), so method A<string>.B.A<int>.B.M will print nearest T.

    A<string>.B.A<int>.B.M will print int

  2. 2016-12-25 10:12

    By the Introduction to Generics T is also available in nested class. That is case with class B which is nested into A. On the other hand C is nested into B and T of the B is available into C. As you can see T of the B is int and method called on C will use int as generic parameter.

  3. 2016-12-25 10:12

    Method M() Always prints type of generic parameter of the parent class of its class:

    So (new A<string>.B.C()).M(); should print generic type of B which is always int. (You can see B is always A<int>)

    Also (new A<string>.B()).M(); should print string because parent of B is A<string>.

  4. 2016-12-25 10:12

    Changing the code slightly:

    public class A<T>
    {
        public class B : A<int>
        {
            public void M() { System.Console.WriteLine(typeof(T)); }
            public class C : A<T>.B { }
        }
    }
    
    public class P
    {
        public static void Main()
        {            
            (new A<string>.B.C()).M(); //Outputs System.String
        }
    }
    

    Note how I changed C's base class from B to A<T>.B. This changes the output from System.Int32 to System.String.

    Without that, A<string>.B.C derives not from A<string>.B, but from A<int>.B, causing the behaviour you've seen. That's because in general, names defined in base classes are available by unqualified lookup, and the name B is defined in the base class A<int>.

  5. 2016-12-29 15:12

    Can someone explain to me why the below code outputs what it does?

    The crux of the matter is determining the meaning of B in class C : B. Consider a version without generics: (for brevity I'll omit the publics.)

    class D { class E {} }
    class J {
      class E {}
      class K : D {
        E e; // Fully qualify this type
      }
    }
    

    That could be J.E or D.E; which is it? The rule in C# when resolving a name is to look at the base class hierarchy, and only if that fails, then look at your container. K already has a member E by inheritance, so it does not need to look at its container to discover that its container has a member E by containment.

    But we see that the puzzle has this same structure; it's just obfuscated by the generics. We can treat the generic like a template and just write out the constructions of A-of-string and A-of-int as classes:

    class A_of_int 
    {
      class B : A_of_int
      {
        void M() { Write("int"); }
        class C : B { } // A_of_int.B
      }
    }
    class A_of_string
    {
      class B : A_of_int
      {
        void M() { Write("string"); }
        class C : B {} // still A_of_int.B
      }
    }
    

    And now it should be clear why A_of_string.B.M() writes string but A_of_string.B.C.M() writes int.

Leave a reply to - Inheritance and Generic Type Setting

◀ Go back