From the language specification:
4.4 Constructed types
A generic type declaration, by itself,
denotes an unbound generic type that
is used as a “blueprint” to form many
different types, by way of applying
type arguments. The type arguments are
written within angle brackets (< and> )
immediately following the name of the generic type. A type that includes
at least one type argument is called a
constructed type. A constructed type
can be used in most places in the
language in which a type name can
appear. An unbound generic type can
only be used within a
typeof-expression (§7.6.11).
[...]
4.4.2 Open and closed types
All types can be classified as either
open types or closed types. An open
type is a type that involves type
parameters. More specifically:
• A
type parameter defines an open type.
• An array type is an open type if and
only if its element type is an open
type.
• A constructed type is an open
type if and only if one or more of its
type arguments is an open type. A
constructed nested type is an open
type if and only if one or more of its
type arguments or the type arguments
of its containing type(s) is an open
type.
A closed type is a type that is
not an open type.
[...]
4.4.3 Bound and unbound types
The term unbound type refers to a non-generic
type or an unbound generic type. The
term bound type refers to a
non-generic type or a constructed
type. An unbound type refers to the
entity declared by a type declaration.
An unbound generic type is not itself
a type, and cannot be used as the type
of a variable, argument or return
value, or as a base type. The only
construct in which an unbound generic
type can be referenced is the typeof
expression (§7.6.11).
Here's an example I thought of:
// Foo<T> is an unbound generic type.
class Foo<T> { .. }
// Bar<K> is an unbound generic type.
// Its base-class Foo<K> is a constructed, open generic type.
class Bar<K> : Foo<K> { .. }
// IntFoo is not a generic type.
// Its base-class Foo<int> is a constructed, closed generic type.
class IntFoo : Foo<int> { .. }
And here's an attempt to tie that in with the reflection API, using the relevant properties: IsGenericType
, IsGenericTypeDefinition
and ContainsGenericParameters
(These tests are not 100% predictive of each "kind" as per the language spec).
+----------+---------------------+-----------+--------------+-------------------+
| Name | Kind | IsGenType | IsGenTypeDef | ContainsGenParams |
+----------+---------------------+-----------+--------------+-------------------+
| Foo<> | Unbound | TRUE | TRUE | TRUE |
| Foo<>* | Constructed, open | TRUE | FALSE | TRUE |
| Foo<int> | Constructed, closed | TRUE | FALSE | FALSE |
| IntFoo | Not generic | FALSE | FALSE | FALSE |
+----------+---------------------+-----------+--------------+-------------------+
* = Bar<>'s base type.
IsGenericType
. For the new propertyIsConstructedGenericType
see a thread I just started. – Violoncellist