I know that Java implements parametric polymorphism (Generics) with erasure. I understand what erasure is.
I know that C# implements parametric polymorphism with rei
Reification means generally (outside of computer science) "to make something real".
In programming, something is reified if we're able to access information about it in the language itself.
For two completely non-generics-related examples of something C# does and doesn't have reified, let's take methods and memory access.
OO languages generally have methods, (and many that don't have functions that are similar though not bound to a class). As such you can define a method in such a language, call it, perhaps override it, and so on. Not all such languages let you actually deal with the method itself as data to a program. C# (and really, .NET rather than C#) does let you make use of MethodInfo
objects representing the methods, so in C# methods are reified. Methods in C# are "first class objects".
All practical languages have some means to access the memory of a computer. In a low-level language like C we can deal directly with the mapping between numeric addresses used by the computer, so the likes of int* ptr = (int*) 0xA000000; *ptr = 42;
is reasonable (as long as we've a good reason to suspect that accessing memory address 0xA000000
in this way won't blow something up). In C# this isn't reasonable (we can just about force it in .NET, but with the .NET memory management moving things around it's not very likely to be useful). C# does not have reified memory addresses.
So, as refied means "made real" a "reified type" is a type we can "talk about" in the language in question.
In generics this means two things.
One is that List<string>
is a type just as string
or int
are. We can compare that type, get its name, and enquire about it:
Console.WriteLine(typeof(List<string>).FullName); // System.Collections.Generic.List`1[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
Console.WriteLine(typeof(List<string>) == (42).GetType()); // False
Console.WriteLine(typeof(List<string>) == Enumerable.Range(0, 1).Select(i => i.ToString()).ToList().GetType()); // True
Console.WriteLine(typeof(List<string>).GenericTypeArguments[0] == typeof(string)); // True
A consequence of this is that we can "talk about" a generic method's (or method of a generic class) parameters' types within the method itself:
public static void DescribeType<T>(T element)
{
Console.WriteLine(typeof(T).FullName);
}
public static void Main()
{
DescribeType(42); // System.Int32
DescribeType(42L); // System.Int64
DescribeType(DateTime.UtcNow); // System.DateTime
}
As a rule, doing this too much is "smelly", but it has many useful cases. For example, look at:
public static TSource Min<TSource>(this IEnumerable<TSource> source)
{
if (source == null) throw Error.ArgumentNull("source");
Comparer<TSource> comparer = Comparer<TSource>.Default;
TSource value = default(TSource);
if (value == null)
{
using (IEnumerator<TSource> e = source.GetEnumerator())
{
do
{
if (!e.MoveNext()) return value;
value = e.Current;
} while (value == null);
while (e.MoveNext())
{
TSource x = e.Current;
if (x != null && comparer.Compare(x, value) < 0) value = x;
}
}
}
else
{
using (IEnumerator<TSource> e = source.GetEnumerator())
{
if (!e.MoveNext()) throw Error.NoElements();
value = e.Current;
while (e.MoveNext())
{
TSource x = e.Current;
if (comparer.Compare(x, value) < 0) value = x;
}
}
}
return value;
}
This doesn't do lots of comparisons between the type of TSource
and various types for different behaviours (generally a sign that you shouldn't have used generics at all) but it does split between a code path for types that can be null
(should return null
if no element found, and must not make comparisons to find the minimum if one of the elements compared is null
) and the code path for types that cannot be null
(should throw if no element found, and doesn't have to worry about the possibility of null
elements).
Because TSource
is "real" within the method, this comparison can be made either at runtime or jitting time (generally jitting time, certainly the above case would do so at jitting time and not produce machine code for the path not taken) and we have a separate "real" version of the method for each case. (Though as an optimisation, the machine code is shared for different methods for different reference-type type parameters, because it can be without affecting this, and hence we can reduce the amount of machine code jitted).
(It's not common to talk about reification of generic types in C# unless you also deal with Java, because in C# we just take this reification for granted; all types are reified. In Java, non-generic types are referred to as reified because that is a distinction between them and generic types).
Reification is an object-oriented modeling concept.
Reify is a verb that means "make something abstract real".
When you do object oriented programming it's common to model real world objects as software components (e.g. Window, Button, Person, Bank, Vehicle, etc.)
It's also common to reify abstract concepts into components as well (e.g. WindowListener, Broker, etc.)
As duffymo already noted, "reification" isn't the key difference.
In Java, generics are basically there to improve compile-time support - it allows you to use strongly typed e.g. collections in your code, and have type safety handled for you. However, this only exists at compile-time - the compiled bytecode no longer has any notion of generics; all the generic types are transformed into "concrete" types (using object
if the generic type is unbounded), adding type conversions and type checks as needed.
In .NET, generics are an integral feature of the CLR. When you compile a generic type, it stays generic in the generated IL. It's not just transformed into non-generic code as in Java.
This has several impacts on how generics work in practice. For example:
SomeType<?>
to allow you to pass any concrete implementation of a given generic type. C# cannot do this - every specific (reified) generic type is its own type.object
. This can have a performance impact when using value types in such generics. In C#, when you use a value type in a generic type, it stays a value type.To give a sample, let's suppose you have a List
generic type with one generic argument. In Java, List<String>
and List<Int>
will end up being the exact same type at runtime - the generic types only really exist for compile-time code. All calls to e.g. GetValue
will be transformed to (String)GetValue
and (Int)GetValue
respectively.
In C#, List<string>
and List<int>
are two different types. They are not interchangeable, and their type-safety is enforced in runtime as well. No matter what you do, new List<int>().Add("SomeString")
will never work - the underlying storage in List<int>
is really some integer array, while in Java, it is necessarily an object
array. In C#, there are no casts involved, no boxing etc.
This should also make it obvious why C# can't do the same thing as Java with SomeType<?>
. In Java, all generic types "derived from" SomeType<?>
end up being the exact same type. In C#, all the various specific SomeType<T>
s are their own separate type. Removing compile-time checks, it's possible to pass SomeType<Int>
instead of SomeType<String>
(and really, all that SomeType<?>
means is "ignore compile-time checks for the given generic type"). In C#, it's not possible, not even for derived types (that is, you can't do List<object> list = (List<object>)new List<string>();
even though string
is derived from object
).
Both implementations have their pros and cons. There's been a few times when I'd have loved to be able to just allow SomeType<?>
as an argument in C# - but it simply doesn't make sense the way C# generics work.
Reification is the process of taking an abstract thing and creating a concrete thing.
The term reification in C# generics refers to the process by which a generic type definition and one or more generic type arguments (the abstract thing) are combined to create a new generic type (the concrete thing).
To phrase it differently, it is the process of taking the definition of List<T>
and int
and producing a concrete List<int>
type.
To understand it further, compare the following approaches:
In Java generics, a generic type definition is transformed to essentially one concrete generic type shared across all allowed type argument combinations. Thus, multiple (source code level) types are mapped to one (binary level) type - but as a result, information about the type arguments of an instance is discarded in that instance (type erasure).
In C# generics, the generic type definition is maintained in memory at runtime. Whenever a new concrete type is required, the runtime environment combines the generic type definition and the type arguments and creates the new type (reification). So we get a new type for each combination of the type arguments, at runtime.
In C++ templates, the template definition is maintained in memory at compile time. Whenever a new instantiation of a template type is required in the source code, the compiler combines the template definition and the template arguments and creates the new type. So we get a unique type for each combination of the template arguments, at compile time.