Pop Quiz!


Ok class.  Get out your pencils for another pop quiz. 
Without using anything beyond your mind, answer the following question:



Consider the following code:

public class A<T1>
{
public T1 a;

public class B<T2> : A<T2>
{
public T1 b;

public class C<T3> : B<T3>
{
public T1 c;
}
}
}
class PopQuiz
{
static void Main()
{
A<int>.B<char>.C<bool> o = new A<int>.B<char>.C<bool>();
System.Console.WriteLine(o.a.GetType().FullName);
System.Console.WriteLine(o.b.GetType().FullName);
System.Console.WriteLine(o.c.GetType().FullName);
}
}

What gets printed when you execute the Main method of PopQuiz?  No cheating!



Try to figure it out.  Write down what you think the answer is, then check it versus what the actual compiled code prints.



For those of you who didn’t get it… can you figure out why the answer looks the way it does?

For those of you who did get it (and probably cheated then πŸ˜‰  ), can you explain how you got your answer?



Good luck!



Comments (31)

  1. Chris Martin says:

    Should be and is confirmed to be:

    System.Bool

    System.Char

    System.Int32 (unless 64 bit OS?)

    —-

    The reason is that the object o is really an instance of C<int,char,bool> : B<char,bool>

    And B is really defined as B<char,bool> : A<bool>

    So what really has happened is kinda like a this.

    public static void Main()

    {

    A.B.C o = new A.B.C(0,’b’,true);

    System.Console.WriteLine(o.a.GetType().FullName);

    System.Console.WriteLine(o.b.GetType().FullName);

    System.Console.WriteLine(o.c.GetType().FullName);

    System.Console.ReadLine();

    }

    public class A

    {

    public bool a;

    public A(bool a)

    {

    this.a = a;

    }

    public class B : A

    {

    public char b;

    public B(char b, bool a) : base(a)

    {

    this.b = b;

    }

    public class C : B

    {

    public int c;

    public C(int c, char b, bool a) : base(b,a)

    {

    this.c = c;

    }

    }

    }

    }

    That was totally theory and I was surprised that I was right according to the MSIL. πŸ™‚

  2. CyrusN says:

    Chris: "The reason is that the object o is really an instance of C<int,char,bool> : B<char,bool> "

    Why is that the case? πŸ™‚

  3. Annoni says:

    Because some people really hate us, and take great plesure at seeing us squirm. πŸ™‚

  4. geoff.appleby says:

    Actually, won’t you end up with:

    System.Int32

    System.Int32

    System.Int32

    while classes A, B and C were used such that

    T1:=int, T2:=char, T3:=bool

    public fields a, b and c were all declared T1, so therefore we get three ints.

    I haven’t run it to check though, but they’d _have_ to be all the same.

  5. geoff.appleby says:

    Oh, bum. The answer I just gave is the answer that Cyrus was hoping someone would give, right?

    I think I’m wrong now. *laughs*

  6. Geoff: Yeah, I’m in the same boat. The answer seems simple, but I know there’s a catch – I just don’t know what it is.

    By my reasoning, we instantiate a C<bool>, which inherits from B<bool> which inherits from A<bool> – so we should get System.Boolean for all three printouts. Naturally, though, we don’t.

    I’ve gone over the solution Chris Martin gave, and I still don’t understand it. I assume the fact that C<bool> turns into C<int,char,bool> has something to do with them being inner classes, but I have no idea why.

  7. I still think they should all be int32 and if they’re not I can’t understand why at all.

    I agree with Chris Martin that we do actually end up with C<int,char,bool> because the type parameters of the containing type *do* have to propagate downward into the inner types. But all three variables are declared as T1 which is the type parameter of A, not B or C, and thus should be int throughout.

    The only thing I can think of is that perhaps because B<T2> inherits from A<T2>, somehow the definition of T1 is coming from the inheritance relationship, not the containment one. But that seems very wrong, and if it *is* the case, I’d be inclined to think it’s a compiler bug – or a language spec bug – unless I saw *strong* evidence that there’s a reason it should work this way.

  8. Chris Martin says:

    Chris: "The reason is that the object o is really an instance of C<int,char,bool> : B<char,bool> "

    Cyrus: "Why is that the case? :)"

    I’m just picking straws here but, I’m willing to say it’s because C is a public type nested within B which is a nested public type within the public type A. </whew> πŸ™‚

    Type C is a B which is an A. We’re not really instantiating a type A or a type B. We’re instantiating an object of type C that happens to need three generic types as it’s definition because of it’s signature. So the generic types kind of just roll down into C.

    I’m afraid if that’s wrong, I have no idea πŸ˜‰

  9. Oh man, this is a confusing one.

    I went through several phases – the view expressed in my last comment, then agreement with Anver that they should all be bool, and then I wrestled with it even more and concluded that actually Chris is right after all (as expected, he apparently tested it to confirm)

    Even with his explanation it took a LONG time for me to grok what’s actually going on. I’m not really adding anything over what Chris already said, but perhaps if I go through the steps in more detail it’ll be easier for everyone, me included, to understand.

    As I said already, I understood and agreed that we end up with C<int,char,bool> (so that inside C, T1 is int, T2 is char, and T3 is bool). This is necessary so that inside C there are separate notions of T1, T2 and T3, which you need to have. What I didn’t understand to start with was how T1 could be anything other than int in that scenario.

    The reason, as I was so dimly aware in my previous comment, is that A and B are both carrying out two different roles, from the perspective of C. They are containing classes, and they are also base classes. In these two roles they have *different* type parameters. In the role as a containing class, T1 is int for all of them, T2 is char, and T3 is bool.

    So the member variable "c" is of type int because that’s what T1 is at that level.

    However, C inherits from B<T3> (or, more precisely, B<T2,T3> because the value of T2 inside the code of C is shared with the code of B). So in its role as a *base* class, B is instantiated with T1=char and T2=bool. Since the member variables of o come from its base classes (these aren’t static variables, after all), the "b" member variable is of type char.

    And B inherits from A<T2> (no extra implicit type parameters here because A is the top level). Remember that at this point T2 is bool, as mentioned in the last paragraph. So in its role as a base class, A is being instantiated with T1=bool, and so the member variable "a" is a bool.

    Hence the output: Boolean, Char, Int32.

    Insanely complex, but it makes perfect sense. I hereby vow never to write code where an inner generic type inherits from its containing class with a different type parameter!

  10. Chris Martin says:

    Man. I didn’t explain that very well at all πŸ˜‰

  11. Chris Martin says:

    Stuart: "I hereby vow never to write code where an inner generic type inherits from its containing class with a different type parameter!"

    Hear hear!!!

  12. I kind of glossed over why C really inherits from B<T2, T3> rather than, say, B<T1, T3>. I’m finding it hard to figure out a clear explanation for this, but I think it’s because B inherits from A<T2>, but I can’t quite find the words to describe why this should mean that B gets its T1 bound to C’s T2. Any help from anyone?

  13. I still haven’t figured this out, but I’ve thought about it some more and I’m even less sure as to where the "char" in the middle comes from – or rather, why it should be char rather than int, bool, or actually almost anything else at all.

    The confusion in previous explanations partly comes from the fact that we’re using the type names Tn to mean different things, in our explanations.

    When we say that B is really B<T1,T2> that’s not exactly what we mean. In a pseudolanguage that doesn’t have C#’s automatic inheritance of type parameters from containing classes, you could express the situation like this:

    public class A<AT1> {

    public AT1 a;

    public class B<BT1,BT2> : A<BT2> {

    public BT1 b;

    public class C<CT1,CT2,CT3> : B<???,CT3> {

    public CT1 c;

    }

    }

    }

    Note the ??? – it’s not obvious what to put in that location. We can’t derive it by analogy from the relationship between A and B – there’s no equivalent problem there because all the parameters are explicit. We might want to say "BT1", but in our imaginary language this isn’t valid code because BT1 is not available in the declaration of C; our imaginary language specifically doesn’t support the inheritance of type parameters into nested types, as in B to C. The only available options are CT2 and CT1.

    The question, then, is why the C# compiler chooses to put CT2 there, rather than CT1. And I still haven’t come up with a good answer. CT1 seems like it would make more sense – in the real C# code, after all, the variable in question is called T1 in both cases. And there doesn’t appear to be any firm constraint requiring it to be anything at all – as far as I can see the compiler could equally well choose to put Foobar or object in there.

    I’m sure I’m missing some scenario in which the selection of CT2 is logical and necessary by some set of constraints. But I can’t for the life of me figure out what it is.

  14. I’ve *finally* figured it out, I think. I didn’t spot it sooner because once I’d translated into the imaginary language where inheritance of type parameters is explicit, I stopped thinking about the C# meaning of such things.

    In the imaginary language you have C<T1,T2,T3> but C# doesn’t see it that way. C# sees it as A<T1>.B<T2>.C<T3>.

    So the problematic line can be re-imagined from C#’s point of view like this:

    public class C<T3> : A<???>.B<T3>

    When expressed this way, it’s clear that we need to consider what instantiations of A are in scope at this point in the code. As we’ve already established, there are two – one is as a containing class, and the other is from the fact that A is the base class of B. The containing class is A<T1>, and the base class of B is A<T2>.

    In other words, when the compiler sees B<T3>, it’s a potentially ambiguous expression, since there is a B in the scope of the base class (A<T2>.B) and a B in the scope of the containing class (A<T1>.B). At this point the regular rules of the C# language have to come into play to decide which one takes precedence.

    Unfortunately the C# language specification as shipped with VS2003 doesn’t provide any specific insight into this situation (the bit about hiding doesn’t mention nested types, and the bit about nested types only talks about the nested type itself hiding a member of the base class) but I’d be willing to bet that there’s a general rule that members of a base type hide equally-named members of the containing type.

    Which means we get A<T2>.B<T3> which translates under the hood into B<char,bool> and explains everything πŸ™‚

  15. CyrusN says:

    Geoff: "Oh, bum. The answer I just gave is the answer that Cyrus was hoping someone would give, right? "

    Yes πŸ˜‰

    "I think I’m wrong now. *laughs*"

    Don’t worry about it. I was able to convince myself of 3 different contradictory solutions to this. ANd the right one was the one i thought was least likely.

  16. CyrusN says:

    Stuart:

    "

    public class A<AT1> {

    …public AT1 a;

    …public class B<BT1,BT2> : A<BT2> {

    ……public BT1 b;

    ……public class C<CT1,CT2,CT3> : B<???,CT3> {

    ………public CT1 c;

    ……}

    …}

    }

    "

    Spot on.

  17. CyrusN says:

    Stuart: If you check out the ECMA spec http://download.microsoft.com/download/8/1/6/81682478-4018-48fe-9e5e-f87a44af3db9/standard.pdf

    Section 10.8, you’ll see all the rules spelle dout in gory detail. They specify how name lookup occurrs, and why

    C : A<T2>.B<T3>

  18. loc says:

    I didn’t get the answer, since I’ve just read about generics for the 1st time half an hour ago. But I looked at people’s answers, and Int32x3 seemed to "make sense" a little bit more.

    A<int>.B<char>.C<bool> o = new A<int>.B<char>.C<bool>();

    Thus, this means T1 is int.

    And since a, b, and c are T1; therefore, they are int.

    Not an in-depth explanation so correct me if I’m wrong. =)

  19. Martin says:

    I’m lost. I’ve read section 10.8 of the ECMA spec over and over again, but I still can’t derive the correct answer from that. Chris wrote in his comment that "o is really an instance of C<int,char,bool> : B<char,bool>". But why? And which part of the spec describes that fact?

  20. loc, if you read my comments, you’ll see that I initially thought the same thing, and then gradually came to the realization that I was wrong and why.

    Martin, again, my comments went through my gradual path to enlightenment in some detail so you might be able to see what’s going on by reading them carefully. I’m not going to repeat everything in there (in particular, the reason it’s B<*char*,bool> is particularly subtle) but I will explain why C is really C<int,char,bool> and not just C<bool> as it might appear.

    The underlying MSIL language only has a limited understanding of nested types (actually I’m not completely sure it has any concept of nested types at all). So in order to compile C# code like:

    class A {

    class B {

    }

    }

    the C# compiler translates it under the hood into something like:

    class A {

    }

    class A.B {

    }

    This is straightforward in the 1.x world, but when you add generics it’s a little more complicated. In this case we have:

    class A<T1> {

    class B<T2> {

    T1 b;

    }

    }

    The C# compiler could try to translate it like this:

    class A<T1> {

    }

    class A.B<T2> {

    ??? b;

    }

    but obviously that doesn’t work because of the ???. T1 isn’t in scope at this point.

    So instead the compiler does this:

    class A<T1> {

    }

    class A.B<T1,T2> {

    T1 b;

    }

    That’s all relatively straightforward and it’s necessary for the implementation of nested types in the presence of generics. The extension of this to the fact that C is really A.B.C<T1,T2,T3> is obvious.

    The evil aspect of this particular example is that as well as containing each other, A B and C also *inherit* from each other. This produces all sorts of subtleties and the best way I can suggest to understand them is to read my previous comments in which I gradually figured out what’s going on.

  21. CyrusN says:

    Martin: "I’m lost. I’ve read section 10.8 of the ECMA spec over and over again, but I still can’t derive the correct answer from that. Chris wrote in his comment that "o is really an instance of C<int,char,bool> : B<char,bool>". But why? And which part of the spec describes that fact?"

    Specifically:

    o Otherwise, if the namespace-or-type-name appears within the body of a type declaration, then for each instance type T (§25.1.2), starting with the instance type of that type declaration and continuing with the instance type of each enclosing class or struct declaration (if any):

    β€’ Otherwise, if T contains a nested accessible type having name I and K type parameters, then the namespace-or-type-name refers to that type constructed with the given type arguments. If there is more than one such type, the type declared within the more derived type is selected. [Note: Non-type members (constants, fields, methods, properties, indexers, operators, instance constructors, finalizers, and static constructors) and type members with a different number of type parameters are ignored when determining the meaning of the namespace-or-type-name. end note]

    So here’s how lookup (confusingly) works:

    Remember that we’re starting with:

    C<T3> : B<T3>

    and we’re trying to figure out what C<T3> is trying to derive from.

    "then for each instance type T (§25.1.2), starting with the instance type of that type declaration and continuing with the instance type of each enclosing class or struct declaration"

    So we look at C<T3> and it doesn’t have any nested B<> (not that it would matter since you can’t depend on one of your nested types anyways), so we walk up to the enclosing instance type and we start looking at B<T2>. Now we look at the phrase:

    "β€’ Otherwise, if T contains a nested accessible type having name I and K type parameters, then the namespace-or-type-name refers to that type constructed with the given type arguments."

    and, as it turns out that B<T2> does contain a nested accessible type with that name. HOw??? Well, nested types are accessible through supertypes. So we examine B<T2>’s supertype (which in tihs case is A<T2> and we find the nested type B<>). So we’re done.

    So the super type of C<T3> is A<T2>.B<T3>.

    From that you cna see how all the type params flow.

  22. Orion Adrian says:

    Isn’t complexity and confusion like this what’s responsible for people leaving C++ for Java and .Net? While it’s nice for a trivia question, isn’t this an indication of something bad?

  23. denis says:

    After doing some minimal translation of the code, I actually found it works the same way in C++. The program prints out first bool, then char, then int.

    Indeed, if one makes a minor modification to the program to discover the types of base objects, one finds that:

    o is of type "class A<int>::B<char>::C<bool>"

    o’s B base is of type "class A<char>::B<bool>"

    o’s C base is of type "class A<bool>"

    The confusion arises because the syntax in which the sample program is written is underqualified. It just says "inherit from B<bool>", but it doesn’t say whether this should be "A<int>::B<bool>" with T1=int from the A class containing B; or whether this hould be T1=char from the A class inherited by the B class that contains C.

    It so happens that the A<bool> class inherited by the B class that contains C takes precedence, and so we end up inheriting C from A<char>::B<bool>, not from A<int>::B<bool>.

  24. denis says:

    Sorry, one of the lines in my previous post should be:

    o’s A base is of type "class A<bool>"

  25. loc says:

    finally understood clearly =)

  26. CyrusN says:

    Orion: "Isn’t complexity and confusion like this what’s responsible for people leaving C++ for Java and .Net? While it’s nice for a trivia question, isn’t this an indication of something bad? "

    Absolutely. But, in the case of generics, it was weighing the badness of conceptual overhead and complex type systems, with the goodness of typesafety and other things.

  27. Sean Chase says:

    Woo hoo, I finally get it!!! I had to read the 3rd comment by Cyrus before I could see the picture. It was like one of those magic-eye 3D posters where all of a sudden…WHAM! you can see it. Sheesh, that was rough.

    * head explodes *

  28. denis, you wrote:

    > After doing some minimal translation of the code, I actually found it works the same way in C++. The program prints out first bool, then char, then int.

    Actually this is not right for C++. Actually it should have been "bool, int, int". VC++7.1 is wrong here. VC++8, GCC and Comeau print "bool, int, int" as expected.

  29. ff says:

    [url=http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/]δΈŠζ΅·η‰Ήδ»·ζœΊη₯¨[/url]

    [url=http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>http://www.88tm.com/“>

  30. Today, the answer to Friday’s puzzle . It prints "Int32". But why? Some readers hypothesized that M would