Why is specialization in C# generics limited? - c#

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"?

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.

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.

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++)

Related

When does type checking of generic definitions and instantiations happen in C#?

In C#,
does type checking of generic definitions happen at compile time?
does type checking of instantiations of generics happen at run time?
Thanks.
The above questions are for me to understand the quotes in bold from C# in a Nutshell:
However, with C# generics, producer types (i.e., open types such as
List ) can be compiled into a library (such as mscorlib.dll).
This works because the synthesis between the producer and the
consumer that produces closed types doesn’t actually happen until
runtime.
To dig deeper into why this is the case, consider the Max method in
C#, once more:
static T Max <T> (T a, T b) where T : IComparable<T>
=> a.CompareTo (b) > 0 ? a : b;
Why couldn’t we have implemented it like this?
static T Max <T> (T a, T b)
=> (a > b ? a : b); // Compile error
The reason is that Max needs to be compiled once and work for all
possible values of T . Compilation cannot succeed, because there is
no single meaning for > across all values of T —in fact, not
every T even has a > operator.
I also have the same question for Java.
Both generic definitions and instantiations are checked at compile time. Further, they can be checked separately. Unlike C++ where you may have an error in your template that you don't discover until you later try to instantiate it, in C#, any compile errors in a generic declaration will be found when the declaration itself is compiled.
The magic that enables this that C++ lacks is constraints. This is what the example is showing you.
When you define a generic method or class, you can put constraints on the type parameters. Those limit which instantiations are allowed but also determine what operations you can take advantage of in the body of the generic declaration.
When the declaration is compiled, the compiler checks that you don't do anything with a type parameter that its constraints don't allow. So, for example, you'd get an error here:
T Foo<T> (T a) => a.CompareTo(b);
You're trying to call CompareTo on a, whose type is T. The compiler has no way of knowing that a user will only instantiate Foo with types that do have that method, so it pessimistically assumes it could be instantiated with a type that doesn't have that and prevents you from compiling this declaration.
When you change it to:
T Foo<T> (T a) where T : IComparable => a.CompareTo(b);
Now it knows every instantiation of T must have a CompareTo() method, so it compiles this.
Later when someone tries to instantiate Foo with some type, if the type does not implement IComparable, they get a compile error. Since the method says "You can only use me with types that implement IComparable", the compiler ensures they meet that constraint.
The main purpose of type-checking is to detect errors at compile-time i.e. before the software is used. This prevents bugs from reaching the users.
C# generic types are checked at compile-time. See the Benefits of Generics.
C# also does type-checking at run-time in certain situations, but that is too late to prevent bugs - the application is already running and being used.
Well, the compiler checks the usage of generic argument at compile time and generate “generic aware type”. There rea especial instructions in IL for this kind of operation. C# compiler uses any restriction at the generic definition to allow operation on generic argument. So, answer on the first question – Yes, it does check generic argument type. During definition of regular type based on generic type compiler checks if the type satisfies all restrictions and if so generate another type, internal one, for using with the combination of generic type and arguments. The compiler will use that generated type for all other instances of combination generic type and its argument type, but this, second phase, happened at runtime when request for creating of particular generic type with argument.
When type is created it is just regular, not generic type event the base class of it is generic and compiler uses common approach to check types related to any instance of this type.

Is it possible for C# generic to be covariant and contravariant?

I have read some content on variance, and want to make sure I fully get it. My understanding is that covariant is implicit conversion of the more-derived type to less and contravariance is the opposite. I further believe that covariance corresponds to the out keyword of the generic type definition. For example IEnumerable interface definition uses out to export the result of an argument, while IComperable's interface definition uses the in keyword to receive a value. I have not seen a generic type which does both, and don't see why it may not be possible. What am I missing?
I also read about the array[] support of variance and how there is a runtime check to make sure the types are the same.

What is the proper terminology for each type of an identifier?

Take the following code:
IFoo foo = new FooImplementation();
The identifier foo has two types:
IFoo - This is the type the compiler will enforce. I will only be able to call methods that are part of the IFoo contract, otherwise I'll get a compiler error.
FooImplementation - This is the type as known by the runtime. I can downcast foo to a FooImplementation at runtime, and then call non-IFoo methods of FooImplementation.
My question: What is the proper terminology for these two types. I could swear in school we were taught that IFoo is the identifier's static type and FooImplementation is its dynamic type, but after much searching on Google I can't seem to find any reference to this.
I would call IFoo and FooImplementation the compile-time and run-time types, respectively. This language is used by C# spec, for example, when talking about virtual methods (section 1.6.6.4):
When a virtual method is invoked, the run-time type of the instance for which that invocation takes place determines the actual method implementation to invoke. In a nonvirtual method invocation, the compile-time type of the instance is the determining factor.
I agree with Mike Z. The usual terminology in C# is "compile time type" and "runtime type".
"Static type" and "dynamic type" are entirely reasonable terms but I would avoid them in the context of C#. "Static type" could too easily be confused with "static class", a class which can only contain static methods. And "dynamic type" can too easily be confused with the dynamic type feature added to C# 4.
The first is the Declared Type. The second is the Concrete Type.
...at least that's what I call them.

How do generics implement structs?

I was thinking about this. classes are obviously passed around by ptr. I suspect structs are passed around by copying it but i don't know for sure. (it seems like a waste for an int array to have every element a ptr. and passing ptrs for ints)
But thinking about it, List<MyStruct> can not know the size of my struct. What happens when i do this? Are there multiple copies of "List`1" and every time i use it with a storage size it does not have it creates a new implementation? (adjusting for the new offsets of T and such).
That could make sense since the source would be in the CIL inside of a DLL. But i am completely guessing, how is it done? Perhaps a reference or page # to the ECMA standards?
Generics use the concept of open and closed generic types: A parametrized generic class definition (i.e. List<T>) is an open generic type of which the runtime generates a closed generic type for each different use you have in your code, i.e. a different type is created for List<int> and for List<MyStruct> - for each closed generic type the size and type of T is known at run-time.
Clarification from MSDN:
When a generic type or method is
compiled into Microsoft intermediate
language (MSIL), it contains metadata
that identifies it as having type
parameters. How the MSIL for a generic
type is used differs based on whether
the supplied type parameter is a value
type or reference type.
When a generic type is first
constructed with a value type as a
parameter, the runtime creates a
specialized generic type with the
supplied parameter or parameters
substituted in the appropriate
locations in the MSIL. Specialized
generic types are created one time for
each unique value type that is used as
a parameter.
Generics work somewhat differently for
reference types. The first time a
generic type is constructed with any
reference type, the runtime creates a
specialized generic type with object
references substituted for the
parameters in the MSIL. Then, every
time that a constructed type is
instantiated with a reference type as
its parameter, regardless of what type
it is, the runtime reuses the
previously created specialized version
of the generic type. This is possible
because all references are the same
size.
The CLR compiles 1 version of the generic class and uses it for all reference types. It also compiles 1 version for every value type usage to optimize for performance.

C# Problem with type cast

here's the code,
Type tbn = Type.GetType(dii.DictionaryName);
DictionaryXmlInfo4BaseDictionary<tbn>.AddDictionaryXmlInfo((message));//error
You can't use generics like that. Generics are meant to be used for types known at compile time.
You can do it with reflection - getting the generic DictionaryXmlInfo4BaseDictionary type definition, creating the closed type using Type.MakeGenericType, then calling AddDictionaryXmlInfo on it again by reflection... but it's relatively painful.
You cannot use generics with a type that is known only at runtime. The DictionaryXmlInfo4BaseDictionary<T> type is generic and requires the T argument to be known at compile time if you want to use it.
You can't use an instance of a type as a generic parameter.
The generic parameter should be the whatever the base instance is, DictionaryXmlInfo4BaseDictionary<object> in the most generic case, but you probably want something further down the class hierarchy than that.
You can't use Type in that way, refactor DictionaryXmlInfo4BaseDictionary so it takes a Type paramater as part of the method e.g.
DictionaryXmlInfo4BaseDictionary.AddDictionaryXmlInfo(tbn, message);

Categories