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?
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.
Can someone explain to me why the below code outputs what it does?
I'll explain briefly here; a longer explanation can be found here.
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
.
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>
.
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
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>
.