Reputation: 2114
I'm trying to understand how C# views types in the face of nesting.
More specifically I'm trying to understand why some types are not considered assignment compatible or even castable, when there "kind of" only exist one definition of the nested class. Does the compiler / CLR actually generate different types for these, or what rules are at play exactly...
Example code:
public class Foo<T>
{
protected class Private2 : Private1<Foo<T>>
{ }
protected class Private1<T2> where T2 : Foo<T>
{
public sealed class Nested
{
public void Test(T2 foo)
{
foo.Method2(this); //Nope!
var nes = (Private2.Nested)this; //Nope!
}
}
}
public void Method1()
{
var nested = new Private2.Nested();
nested.Test(this);
}
private void Method2(Private2.Nested nested)
{
// something code...
}
}
So even though the nested instance is created as a Private2.Nested it can not be casted to that type. And... well... how do the different Nested types relate to each other given that Nested is in fact sealed? (They can't be inheriting from each other right? But on the other hand their implementation should be 100% identical... am I wrong?)
Primary question: What exactly is the compiler doing when it "compiles" this nested class?? How many unique types (excluding valuetype-related) are actually generated, and if it is all the "same" type, is the restriction artificial (as in wouldn't an unsafe cast actually work)? (What I'm saying is that the IL for all these types comes from the same code definition - so at some level the compiler must know. Are instances of these types not bit-for-bit identical apart from their type-names?)
Secondary question: not what I'm really asking here, mostly for brevity / context: is there some simple change that would make the above work? Am I missing something obvious?
The type Foo<T>
must never be directly referenced inside Private1<T2>
- only use of T2
is allowed. Foo<T>
is just my example stand in for nasty generic classes with 10~20 generic types. It's all just a "workaround" for not being able to alias a generic class with its types:
public class Bar<GoodName, OtherName, Readability, NopeMr, DontThinkSo, Suffering, Dispair>
{
//If only this was real...
using BarT = Bar<GoodName, OtherName, Readability, NopeMr, DontThinkSo, Suffering, Dispair>;
public void Method1(BarT bar) { ... } //so good!!
//goodbye readability... see you never...
public void Method2(Bar<GoodName, OtherName, Readability, NopeMr, DontThinkSo, Suffering, Dispair> whatIsThisVariable) { ... }
}
Purpose: To avoid types of fields and method-parameters that are several screens wide and utterly unreadable! >:(
...As a side note I really wished this
could be used as a type inside classes and interfaces, as in Private2 : Private1<this>
. Well ok, that wouldn't work because it collides with extension syntax on methods, but something similar, perhaps <this>
, <super>
, <base>
used like Method(<this> arg)
or Private2 : Private1<<super>>
... kind of weird maybe.
Upvotes: 10
Views: 3156
Reputation: 2114
Partial answer to the primary question:
It was bugging me that you can make the code compile by changing Method2 to accept an object and cast it at runtime, because the nested instance is of the correct type (it's instantiated inside Method1). That would seem to work - as long as Foo is sealed - but as soon as someone else can subclass Private1 it is no longer guaranteed to work. (And thus not a solution.) However testing this approach reveals:
Private2.Nested
is only a construct of syntax rules - usingGetType()
on the resulting variable saysPrivate1.Nested
and there is noPrivate2.Nested
type.
I think the irksome feeling I was getting from this (and why I concidered sealed to be related) was some kind of confusion on my part when it came to distinguishing between subtype and inheritance. Because the outer classes are inheriting (Private1 and Private2) it feels like inheritance, and thus it feels like it should somehow be castable. But if I understand this correctly they are merely of the same subtype:
There need not be and is in fact no inheritance relation one way or the other (as the sealed clearly hints) because "the inheritance hierarchy is distinct from from the subtype hierarchy", and thus a downright conversion would be needed (since casts are bound to the inheritance hierarchy).
Upvotes: 0
Reputation: 22122
Consider this types:
public class Base {
public static int Value;
public class Nested { }
}
public class Derived:Base { }
What is Derived.Value
and Derived.Nested
. Actually, when you refer to inherited static members (nested class considered to be static member) thru derived class, you just reference base class members, so this have exactly same meaning as Base.Value
and Base.Nested
at compile time. There are no separate static field Derived.Value
or separate class Derived.Nested
.
public static void Test() {
Derived.Value=10;
Console.WriteLine(Base.Value);
Base.Value=20;
Console.WriteLine(Derived.Value);
Base.Nested bn=new Derived.Nested();
Derived.Nested dn=new Base.Nested();
Console.WriteLine(typeof(Base.Nested).FullName);
Console.WriteLine(typeof(Derived.Nested).FullName);
Console.WriteLine(typeof(Base.Nested)==typeof(Derived.Nested));
}
Original answer:
Foo<A>.Private1<B>.Nested
and Foo<C>.Private1<D>.Nested
considered to be different types if A
!=C
or B
!=D
. They can share same implementation internally, but for assignment compatibility they are different. Foo<T>.Private2.Nested
is just alias to Foo<T>.Private1<Foo<T>>.Nested
. And even if class Bar:Foo<A>{}
, classes Foo<A>.Private1<Foo<A>>.Nested
and Foo<A>.Private1<Bar>.Nested
still considered to be different types. So Foo<T>.Private1<T2>.Nested
can not be converted to Foo<T>.Private1<Foo<T>>.Nested
as T2
is not necessary Foo<T>
.
Upvotes: 2
Reputation: 12425
You're not thinking with portals. Your inner classes are already generalized on T.
public class Foo<T>
{
private class Private2 : Private1
{ }
private class Private1
{
public sealed class Nested
{
public void Test( Foo<T> foo )
{
foo.Method2( this ); //Yup
var nes = (Private2.Nested)this; //Yup
}
}
}
public void Method1()
{
var nested = new Private2.Nested();
nested.Test( this );
}
private void Method2( Private2.Nested nested )
{
// something code...
}
}
Upvotes: 1