An Inheritance Puzzle, Part One

An Inheritance Puzzle, Part One

  • Comments 24

Once more I have returned from my ancestral homeland, after some weeks of sun, rain, storms, wind, calm, friends and family. I could certainly use another few weeks, but it is good to be back too.

Well, enough chit-chat; back to programming language design. Here's an interesting combination of subclassing with nesting. Before trying it, what do you think this program should output?

public class A<T> {
    public class B : A<int> {
        public void M() {
            System.Console.WriteLine(typeof(T).ToString());
        }
        public class C : B { }
    }
}
class MainClass {
    static void Main() {
        A<string>.B.C c = new A<string>.B.C();
        c.M();
    }
}

Should this say that T is int, string or something else? Or should this program not compile in the first place?

It turned out that the actual result is not what I was expecting at least. I learn something new about this language every day.

Can you predict the behaviour of the code? Can you justify it according to the specification? (The specification is really quite difficult to understand on this point, but in fact it does all make sense.)

(The answer to the puzzle is here.)

  • It did what I expected.

    This may spoil it for everyone else, but it's because you're deriving B from the specific A<int>, not the generic A<T>.  So instantiating A with a type T of string doesn't matter to class B.

  • Inner class B always has type int no matter what type the outer class A is declared with.

    Since method M() is part of class B, it will use the meaning of T that B was declared with and ignore the outer T.  Thus the printed type will be int or System.Int32 as was printed when I ran the program.

  • First off I'll admit that if I didn't know this was a trick question I'd assume "string" without a second thought. Knowing that it IS a trick question, though, and thinking about the question a little harder...

    A<string>.B inherits from A<int>

    A<string>.B.C inherits from "B"

    The question is, WHICH "B" does C inherit from? Is it A<T>.B that's in scope because it's the containing class of C (which would lead to output of string), or is it A<int>.B that's in scope because A<int> is the base class of the containing class?

    I can't readily justify why A<int>.B inherited from the base class of the container should have higher precedence in scope than A<T>.B which *is* the container, though. So I'll completely punt on actually answering the question, but give justifications for each of the possible outcomes:

    string: because A<T>.B.C inherits from A<T>.B and T is string.

    failure to compile: because a class can't inherit from it's containing class (I forget if that's actually a rule in C#?) so A<T>.B.C can't inherit from A<T>.B

    int: this is tricky to justify so the best I can come up with is a combination of the prior two - the compiler knows that A<T>.B.C can't inherit from A<T>.B so it discards that possible meaning of "B" and proceeds to the only other meaning in scope, A<int>.B - and makes A<string>.B.C inherit from that.

    I still think string is the most likely option. But I'm probably missing something.

  • jimo and Doug:

    If that's the explanation, then what happens if you put this in Main instead:

    A<string>.B b = new A<string>.B();

    b.M();

  • Stuart: You are right, the question hinges solely upon what "B" means in "class C : B", and in this case it means A<int>.B.  

    It is perfectly legal for a class to inherit from its outer class, and in fact this is a very useful way to write the following pattern:

    public abstract class Frobber {

    private Frobber() {}

    public abstract void Frob();

    public static Frobber MakeFrobber(int size) {

     if (size > 100) return new BigFrobber(size); else return new SmallFrobber();

    }

    private class BigFrobber : Frobber { ... }

    private class SmallFrobber : Frobber { ... }

    }

    This lets you build a factory which hands out frobbers such that you can change what actual object you hand out in future versions without breaking backwards compatibility.  No third party can extend Frobber because its only constructor is private, no third party can get at the concrete classes because they are private, etc.

  • I'll say it prints "string". The fact that B derives from A<int> means that B itself is not a template, so the "<int>" part has no effect on the generated code. The T in B::M() comes from the instantiation of A, which is A<string>. I would expect the code to print "int" only if B derived from A<T>. C is just an attempt at misdirection. :)

  • I don't know what C# does, but I know what I would want it to do.  I would want it to print "string"

    IMHO, it doesn't make sense to me at all that the inherited T (being int) from A<int> in the class declaration for B should mask out the T from generic outer class A.  The generic parameter T should act sort of like a static member, so the int T inherited from A<int> shouldn't be "visible" at all from the scope of B's declaration without specifying something like A.T.

    Just my opinion.

    If I were doing this in C++, I'd create a typedef member in A for T such as...

    class A<T>

    {

    typedef T templ_T;

    // other A members

    }

    ...then only explicitly reference either A::templ_T or B::templ_T, never just T, to avoid any ambiguities.... I don't know if there is any similar option in C#.  It seems to me that to have generics without having some way of creating a type alias could create a lot of hassles.

  • Well, the answer depends on your compiler. MSVC says int, Mono says String.

  • Makes sense to me.

    For our purposes, A<string> is a namespace, not a class.

    Actually, A<string>.B is also a namespace.

    C is the class, and it inherits from B. B in turn inherits from A<int>.

  • > C is the class, and it inherits from B. B in turn inherits from A<int>.

    Right, but in other languages with generics, such as C++, the template parameters _don't_ inherit like other members.  Which makes sense, because even in C#, you can't access them like other members.  In C++, template parameters only "inherit" when you don't specify it for the inner class (i.e. class B : A<T>, instead of class B : A<int>).  But then what you are really doing is not inheriting, but rather declaring a new template parameter T for B and assigning it the same value as the outer T from A.

    The way I look at it, B shouldn't have a template parameter T received from A<int>, because A<int> is completely specialized for int.  It's not generic anymore.  If it was still generic, you'd have to specify the value for both A and B (e.g. A<string>.B<int>.C, and in that case, the output of int makes perfect sense).  And if B is derived instead from A<T>, then B gets a new T which _happens_ to have the same value as the outer one.

  • Here's a shorter version of the same problem:

    public class A<T>

    {

    public class B : A<int>

    {

    public void L() { System.Console.WriteLine(typeof(T).ToString());  }

    }

    }

    class MainClass

    {

    static void Main()

    {

    new A<bool>.B.B.B.B().L();

    new A<bool>.B().L();

    System.Console.ReadKey();

    }

    }

  • And you don't need generics to demonstrate the issue either:

    namespace Root

    {

    class A{  class X{  class B{  class X{  class C : A{

      X a = new Root.A.X();//this is OK

      X b = new Root.A.X.B.X(); //this is not OK

      X c = new Root.A.X.B.X.C.X(); //this is OK

    }}}}}

    }

    So the point is - inside the innermost class C, how is syntactic token X resolved?

    is it Root.A.X, or Root.A.X.B.X?

    When referring to X, you first look at whether such a name exists INSIDE the current scope.  So inside C, does C.X exist?   Turns out, it does!  Since C inherits from A, and A contains an "X", C contains and "X".

    declarations scoped inside a superclass have precedence above syntactically surrounding scopes.

    This is acutally exactly what causes the original oddness:  When in Eric Lipperts code, inside class B, a new class C is declared, it is subclassed from B - but from WHICH B?  You might think that inside class B clearly "B" means the type of that class itself, but as the non-generic example illustrates, there's another "B", namely that in scope due to inheritance.  The inherited "B" takes precedence over the "B" in surrounding scopes, just like in the non-generic case.

  • And of course the non-generic example works in Mono. So I guess I'll file another bug then (it seems every post of you reveals at least one mono bug ;) ). Also, though it's clear why this B is chosen, it's still a question why inheritance over scoping is the design choice.

    So consider this the example given above concerning the Frobber. Let's say the Frobber contains an inner class called Irquil, because each Frobber has it's own Irquil, which however isn't relevant to any other classes, so it's an inner class of Frobber. However, as it appears, an Irquil is also something which has nothing to do with Frobbers at all, so it also exists somewhere else. Now if you want to create an Irquil in BigFrobber, do you want it to be the inner class or the unrelated class?

    However, why you would create a stack of inner classes inside Frobber, and have the other Irquil somewhere in there is still a bit unclear to me... Really, who invented this whole inner class business. It makes me wanna cry. Also, it's robbing me of my sleep, so i think I call it quits now ;)

  • Well, I'll wait to hear from Mr. Lippert the explanation of all this.

    However, I"ll point out that the "inheritance" of the generic parameter T seems to be a problem to me.  It leads to an _implicitly declared_ generic parameter (T in B) masking an explicitly declared one (T in A).  And since the generic parameter cannot be referenced in a manner like "real" members (i.e. "dot" syntax: A.T), and C# seems to have no way to create a type alias member (like C++ typedef), this seems to me to be a big usability problem for the language itself.  As far as I can see, we have an implicit masking which cannot be gotten around without changing the class structure in a fairly significant way.

  • First off, Mr. Lippert is my father, dude.

    Second, I do not know what you mean by "the inheritance of the generic parameter T".   T isn't inherited.  B doesn't have a T.  Only A<T> has a T.

    Hopefully my explanation on Monday will clear it all up.

Page 1 of 2 (24 items) 12