2

I have this code as an example:

class A<T> {
    public class B : A<long> {
        public void f() {
            Console.WriteLine( typeof(T).ToString());
        }
 
    public class C : A<long>.B { }
    
    } 
}

class Prg5 { static void Main() {
    var c = new A<float>.B.C();
    c.f();
} }

With output:

System.Int64

Why does it print the type for long? How and where does the float type passed originally get replaced?

Reccho
  • 97
  • 6
  • 9
    Because `B` **is** an `A`? – MakePeaceGreatAgain Jan 29 '21 at 10:39
  • 2
    so is using float in "new A.B() irrelevant? Could any type be put in and just be replaced with long? Shouldn't that return some error? – Reccho Jan 29 '21 at 10:41
  • 1
    Seems weird to me also. I would expect `new A.B.C()` to work as well, but apparently it does not. – MakePeaceGreatAgain Jan 29 '21 at 10:44
  • 1
    I agree, it isn't obvious. – vernou Jan 29 '21 at 10:45
  • To simplify things: you don´t even need `C` and also `B` has not to inherit `A`. It´s enough to nest `B` within a generic class to reproduce the issue. – MakePeaceGreatAgain Jan 29 '21 at 10:47
  • 2
    `C` is an `A.B` so `T` inside `f()` is `long`. The `float` in `new A.B.C()` is not used. – heijp06 Jan 29 '21 at 10:51
  • I see, so the type of A is just dropped. Thank you all. – Reccho Jan 29 '21 at 10:56
  • Sorry if I missed some convention, I'm not sure why the question was voted down when it's asking to clear up a confusing example and at least two other people agreed that it was confusing. @OlivierRogier I'm not trying to output anything different, I just wanted to understand why it acts the way it does. Should I still delete it? – Reccho Jan 29 '21 at 10:57
  • 1
    @OlivierRogier just omit all the method clunky stuff and the inheritance. Just imagine it was a generic class containing a non-generic one. To instantiate the latter, you´d need to provide the genric argument of the outer one, even if that argument is of no use within the nested class. – MakePeaceGreatAgain Jan 29 '21 at 11:05
  • Thanks everyone! I would close the question but I don't have the rep yet. – Reccho Jan 29 '21 at 11:12

2 Answers2

4

The type C is defined as:

public class C : A<long>.B { }

The type A is defined as:

class A<T> {
    public class B : A<long> {
        public void f() {
            Console.WriteLine( typeof(T).ToString());
        }    

    public class C : A<long>.B { }
    } 
}

So if you create a new C then the type parameter for A is long.

In the statement var c = new A<float>.B.C();, A<float> and B are just parts of the "path" to the nested class C. The fact that A<float> is part of that path does not change the fact that C is an A<long>.B.

The float parameter and the fact that B is an A<long> are not relevant to determine the type of T inside c.f().

heijp06
  • 11,558
  • 1
  • 40
  • 60
  • 3
    "are just parts of the "path" to the nested class C" That´s the actual answer, IMHO. – MakePeaceGreatAgain Jan 29 '21 at 11:07
  • 1
    I see, so it just needs "anything" since the parent class is generic. I could use `A>.B.C()` and it wouldn't change anything. – Reccho Jan 29 '21 at 11:10
  • Yes and `B` can be an `A`. The fact that `C` is an `A.B` determines the type of `T` in `c.f()`. – heijp06 Jan 29 '21 at 12:35
  • What's weird is that if `C` **does not** inherit from `B` and has it's own `f()`, then it returns "Single". The MSIL is exactly the same: `ldtoken A<>.T` – Charlieface Jan 29 '21 at 14:10
1

Answer

When you call f, you are in a A<T> where T is long because B is a A<long> so T is long.

I see why it seems to be weird: you expect that T is float, but in fact since B is A<long>, B is a A<T> where T is long.

And because C is a child of B, it is an extended same type of B so a A<long>.

Therefore the result for the closed constructed type that output long and not float that was specified for the generic type parameter of the outer class.

So whatever the T specified when creating an instance, you will always get long for the type of T in f.

Solving

It's a little complicated recursive innering, I think, but don't you just simply need that?

class A<T>
{
  public class B : A<T>
  {
    public void f()
    {
      Console.WriteLine(typeof(T).ToString());
    }

    public class C : B { }

  }
}

Thus writing:

var c = new A<float>.B.C();
c.f();

Will output:

System.Single

Readings

Generics open and closed constructed types

Constructed Types

Generics in .NET

Generics (C# Programming Guide)

C# Generics Level 1

C# Generics Level 2

  • 2
    I'm not looking to change anything, I just want to understand why it outputs "System.Int64" as it does. – Reccho Jan 29 '21 at 10:53