5

The question "What is reification?" has a comment on C#'s generics:

Type information is maintained, which allows specialization to an extent, by examining type arguments using reflection. However, the degree of specialization is limited, as a result of the fact that a generic type definition is compiled before any reification happens (this is done by compiling the definition against the constraints on the type parameters - thus, the compiler has to be able "understand" the definition even in the absence of specific type arguments).

  • What does it mean by "specialization"? Is it not the same as instantiation of a generic type with a specific type argument?

  • What does it mean by "the degree of specialization is limited"?

  • Why is it "a result of the fact that a generic type definition is compiled before any reification happens"?

Theodoros Chatzigiannakis
  • 28,773
  • 8
  • 68
  • 104
  • 2
    Why didn't you ask the author? –  Sep 19 '17 at 16:06
  • 2
    Comment is not a good place for detailed explanation. –  Sep 19 '17 at 16:10
  • 6
    I don't understand "too broad" votes to close on this question. If anything, it is easier to argue the question being too narrow, because it seeks clarifications to a portion of one specific answer related to C#. For the record, I think the question is neither too broad nor too narrow. – Sergey Kalinichenko Sep 19 '17 at 16:39

3 Answers3

3

What does it mean by "specialization"? Is it not the same as instantiation of a generic type with a specific type argument?

Author explains in the portion of his answer dedicated to Java generics that

specialization of a generic type [is] the ability to use specialized source code for any particular generic argument combination.

In other words, it is an ability to do something special if a generic type parameter is of a specific type. Supplying an implementation of List<T> that represents individual elements as bits when you instantiate the type as List<bool> would be an example of specialization.

What does it mean by "the degree of specialization is limited"?

Author means that although you can write things like

if (typeof(T) == typeof(bool)) {
    ...
}

your abilities to respond to a combination of type arguments are limited, because any decision on a type combination has to be made at run-time.

Why is it "a result of the fact that a generic type definition is compiled before any reification happens"?

Because reification is done in CLR, well after C# compiler is out of the picture. The compiler must produce a generic type definition for CLR to use as a "template" for making closed constructed types for instances of a generic class.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • 2
    This answer is what I had in mind. I used the term "specialization" not in the sense of applying type arguments to a generic definition, but in the sense of having different code paths for some special types. The reasons you might want to do that usually have to do with performance — e.g you know that a piece of code will be more efficient if you treat a couple of types differently than you would treat others, so you want to provide a different execution path for them. – Theodoros Chatzigiannakis Sep 19 '17 at 16:50
  • 2
    As described in this answer and in my answer in this other question, the parsing of the generic code happens on the side of the C# compiler, while the application of actual generic arguments happens on the side of the CLR JIT-compiler. This has a few implications, like the fact that C# overload resolution must happen on the C# compiler side, so you can't leverage overloading to dispatch to specialized paths from generic code. Instead, you have to do `if`, then `typeof`, then cast, then call the specialized code. And that specialized code needs to be known when the generic definition is built. – Theodoros Chatzigiannakis Sep 19 '17 at 16:50
  • 2
    And that's what I mean by "limited". Just to be clear, I don't use "limited" in a negative sense in my answer. The option of "less limited" generic specialization (such as with C++ templates, where overload resolution happens after the application of the generic arguments) has its own strengths and its own set of problems. – Theodoros Chatzigiannakis Sep 19 '17 at 16:57
  • @TheodorosChatzigiannakis Thank you very much! For Java's generics, "specialization of a generic type (the ability to use specialized source code for any particular generic argument combination) is very restricted" in your reply to the other post. Is it because reflection isn't possible because type argument has been erased at compile time? In Java, since both the parsing of the generic code and the application of actual generic arguments (i.e. type erasure, if I am correct) happen at compile time, does Java's overloading provide "specialization" of a generic type? –  Sep 19 '17 at 18:01
  • @Ben Overload is not possible with Java generics, because all generic types get erased to `java.lang.Object` or to a type specified in a generic constraint. You can still pass `Class` to a generic method or a constructor to enable reflection-based specialization, but the mechanism for doing so is idiomatic, i.e. it is not built into the language. – Sergey Kalinichenko Sep 19 '17 at 18:05
  • @dasblinkenlight In Java, isn't overloading unrelated to generics? Doesn't type erasure only apply to generics but not to overloading? –  Sep 19 '17 at 18:08
  • @Ben I meant you wouldn't be able to do `class G { public void foo(T t) {} public void foo(Q q) {}}` because both `T` and `Q` would get erased to the same type, killing the overload. – Sergey Kalinichenko Sep 19 '17 at 18:19
  • @Ben Conceptually, it's the same principle — the Java compiler must make sense of the generic definition without any specific type arguments (much like the C# compiler). At that time, it has to bind method calls to make sure the code around the call spot is valid (is the return value, if any, treated correctly? are exceptions declared properly? etc). To do all that, overload resolution must happen then and there. – Theodoros Chatzigiannakis Sep 19 '17 at 18:20
  • @Ben The problem arises from the fact that while in C# you can say `if(typeof(T) == typeof(int))` and specialize under that condition, in Java you can't say `if(T.class == Integer.class)`, because you'll get an error ("cannot select from a type variable"). That's because the compiler knows that "type variable" won't make it to the runtime — it's purely a compile-time construct. – Theodoros Chatzigiannakis Sep 19 '17 at 18:28
  • @Ben Thus, in Java, you need some workarounds to be able to check against the type variable, as @dasblinkenlight mentioned. Specifically, you need to include something like a `Class` parameter to the method. Then, of course, you're good to go and do the same stuff you'd do in C#. But I'd say it's more limited in the sense that you need to foresee this need and create your method signature accordingly from the start. – Theodoros Chatzigiannakis Sep 19 '17 at 18:30
  • @dasblinkenlight Thanks. `class G { public void foo(T t) {} public void foo(Q q) {}}`, why not `class G { public void foo(T t) {} public void foo(Q q) {}}` so that the specialized version for `Q` will not get lost, since type erasure applies only to the generic method `foo`? –  Sep 20 '17 at 20:50
  • @Ben This is a little awkward, but I guess you can do that ([demo](https://ideone.com/pbPDzu)). – Sergey Kalinichenko Sep 20 '17 at 20:59
  • @dasblinkenlight Thanks. Back to your reply for C#, I have some difficulty to understand "your abilities to respond to a combination of type arguments are limited, because any decision on a type combination has to be made at compile time." What is it like that a decision on a type combination has to be made at compile time, and what is it like that such a decision can be made at run time? –  Sep 20 '17 at 21:00
  • @Ben I meant to write "at run-time," please see edit. – Sergey Kalinichenko Sep 20 '17 at 23:55
1

I believe the meaning is as follows:

When you define a generic type e.g. MyGenericType<T> your definition has to make sense for any value of T, as the generic type is compiled before you actually use it in a specific implementation ("the degree of specialization is limited, as a result of the fact that a generic type definition is compiled before any reification happens").

Later on, when you actually use a MyGenericType<int> the compiler/jit will create a new class which is pretty much MyGenericType<T> with every mention of T replaced with int. This is the process of reification. This means that at runtime, you can use the fact that the generic type is using an int, but your ability to make use of this (specialisation) is limited, since when you defined MyGenericType<T> you didn't know this.

Yair Halberstadt
  • 5,733
  • 28
  • 60
1

Specialization is used as antonym to generalization. When you created a generic type, you generalized a type definition. When you initialized it with a type, you specialized the compiled generic type to be able to create object of the type at run-time.

IL compiles the generic type. At runtime, this compiled generic type is combined with specific type argument to produce an object of the specified class.

Yes, specialization is same as instantiation of a generic type with a specific type argument at runtime.

With generics, come constraints which basically fix the scope of generic type. You can tell that by defining that T can be a struct, class, or has to have some specific base class etc. You cannot create a class instance which is not allowed by the constraints defined on the generic type.

You can initialize the same generic type definition with a int, string or another class, if it satisfied the constraints in the generic class. It cannot directly create an object of the class with T, not yet replaced by a defined type (primitive types like int, string, or your custom class or interface) and your code inside should be compatible to type being passed in as T for it to work.

Refer (Links from same question you mentioned above):

NET Generics and Code Bloat

Generics are not templates (as in C++)

Amit Kumar Singh
  • 4,393
  • 2
  • 9
  • 22