What Exactly Is an "Open Generic Type" in .Net

What exactly is an open generic type in .NET?

The C# language defines an open type to be a type that's either a type argument or a generic type defined with unknown type arguments:

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.

Therefore T, List<T>, and Dictionary<string,T>, and Dictionary<T,U> are all open types (T and U are type arguments) whereas List<int> and Dictionary<string,int> are closed types.

There's a related concept: An unbound generic type is a generic type with unspecified type arguments. An unbound type can't be used in expressions other than typeof() and you can't instantiate it or call its methods. For instance, List<> and Dictionary<,> are unbound types.

To clarify the subtle distinction between an open type and an unbound type:

class Program {
static void Main() { Test<int>(); }
static void Test<T>() {
Console.WriteLine(typeof(List<T>)); // Print out the type name
}
}

If you run this snippet, it'll print out

System.Collections.Generic.List`1[System.Int32]

which is the CLR name for List<int>. It's clear at runtime that the type argument is System.Int32. This makes List<T> a bound open type.

At runtime, you can use reflection to bind type arguments to unspecified type parameters of unbound generic types with the Type.MakeGenericType method:

Type unboundGenericList = typeof(List<>);
Type listOfInt = unboundGenericList.MakeGenericType(typeof(int));
if (listOfInt == typeof(List<int>))
Console.WriteLine("Constructed a List<int> type.");

You can check whether a type is an unbound generic type (generic type definition) from which you can construct bound types with the Type.IsGenericTypeDefinition property:

Console.WriteLine(typeof(Dictionary<,>).IsGenericTypeDefinition); // True
Console.WriteLine(typeof(Dictionary<int,int>).IsGenericTypeDefinition); // False

To get the unbound type from a constructed type at runtime, you can use the Type.GetGenericTypeDefinition method.

Type listOfInt = typeof(List<int>);
Type list = listOfInt.GetGenericTypeDefinition(); // == typeof(List<>)

Note that for a generic type, you can either have a completely unbound type definition, or a completely bound definition. You can't bind some type parameters and leave others unbound. For instance, you can't have Dictionary<int,> or Dictionary<,string>.

Generics open and closed constructed types

In practice the terminology doesn't really matter much - I can't remember the last time I had to worry about it except when trying to write about it.

  • An unbound type has no type arguments specified
  • A constructed type has at least one type argument specified
  • A type parameter is an open type
  • An array type where the element type is open is an open type
  • An open constructed type has at least one type argument which is an open type
  • A closed type is any type which isn't open

(There are further rules for nested types. Consult the C# 3.0 spec section 4.4 for gory details.)

As an example of an open constructed type, consider:

public class NameDictionary<T> : Dictionary<string, T>

The base class of typeof(NameDictionary<>) is:

  • Constructed because it specifies type arguments
  • Open because the second type argument (T) is an open type

The MSDN docs for Type.IsGenericType have quite a useful little table.

Just to reiterate, this is almost entirely unimportant in day to day use.

I'm generally in favour of knowing the correct terminology - particularly for things like "pass by reference" etc - but in this case it really, really doesn't come up very often. I would like to actively discourage you from worrying about it :)

Registercollection open generic type

To be able to achieve what you want, you will have to make your IFoo<T> interface variant.

The reason you get an empty list is because IFoo<IBar> is not assignable from IFoo<FooBar>. Try it out yourself: the C# compiler will not permit you to cast Foo to IFoo<IBar>.

To achieve this you will have to make IFoo<T> covariant. In other words, you need to define the interface as follows:

public interface IFoo<out T> { }

After you done this, you'll see that Simple Injector automatically resolves all instances for you as you'd expect.

Whether however it is useful for you to have an out type argument is a different question. One that I cannot answer, given the abstract description you gave.

.NET generics terminology - open/closed, unbound/constructed

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.

C# Language: How to get type of bound but open Generic class?

What you are proposing to do is impossible and also will not really help you.

Impossible because...

The documentation states (emphasis mine) that

Types constructed with MakeGenericType can be open, that is, some of
their type arguments can be type parameters of enclosing generic
methods or types
.

This means that you cannot make a Type object representing XClass<int,>. What you can do is:

class Outer<TOuter>
{
class XClass<T, U> {}
}

In this situation, you can make a Type object representing Outer<TOuter>.XClass<int, TOuter>. But there needs to be an enclosing generic class.

Not useful because...

The documentation also states (referring to a similar example to the above) that:

A constructed type such as Base is useful when emitting code,
but you cannot call the MakeGenericType method on this type because it
is not a generic type definition. To create a closed constructed type
that can be instantiated, first call the GetGenericTypeDefinition
method to get a Type object representing the generic type definition
and then call MakeGenericType with the desired type arguments.

Which means that if you have

Type myType = ... // represents Outer<TOuter>.XClass<int, TOuter>

Then to get a Type for XClass<int, string> you would first need to call myType.GetGenericTypeDefinition() (thus losing the int information) and then call MakeGenericType to put it back in (along with the string type parameter). So it's like one step back and two steps forward.

Alternatives

You might want to consider storing the type parameter types for XClass in a separate data structure (e.g. a Type[]) for as long as not all type parameters are known to you, and then create the closed generic type in one go after you have collected all of them.

You can also package all this into a small helper class for convenience:

class GenericTypeDescription
{
private readonly Type openGenericType;
private readonly Type[] typeParameters;

public GenericTypeDescription(Type openGenericType)
{
// add checks for openGenericType actually being what it says here
this.openGenericType = openGenericType;
this.typeParameters = new Type[openGenericType.GetGenericArguments().Length];
}

public void SetTypeParameter(int index, Type type) {
// add error handling to taste
this.typeParameters[index] = type;
}

public Type ConstructGenericType() {
// add error handling to taste
return this.openGenericType.MakeGenericType(this.typeParameters);
}
}

What is the open generic type of the array []?

It's just System.Array; it isn't generic. See here for a brief discussion: http://blogs.msdn.com/b/ericlippert/archive/2007/10/17/covariance-and-contravariance-in-c-part-two-array-covariance.aspx

When is the generic type resolved in c#?

The problem is that the question is not well-posed. Two people are claiming opposite things: that types are "resolved" at runtime and that types are "resolved" at compile time.

Since they are contradicting each other, they must both mean something different by "resolved".

I do not know what it means for a type to be "resolved". I do know however what overload resolution is. When asked to solve an overload resolution problem that does not involve dynamic, the C# compiler determines which overload to call at compile time, based on the compile time information about the generic type. So for example, if you have:

static void Main()
{
var d = new D();
var p = new P<D>();
p.N(d);//Displays In class B
}


class B
{
public void M()// Note, not virtual
{
Console.WriteLine("In class B");
}
}

class D : B
{
public new void M()// new, not overload
{
Console.WriteLine("In class D");
}
}

class P<T> where T : B
{
public void N(T t)
{
t.M();
}
}

N always calls B.M even if P<T> is instantiated as P<D>. Why? Because the overload resolution problem that determines what the meaning of t.M is must be solved when P<T>.N is compiled, and at that time, the best the compiler knows is that t must be B, so it chooses B.M.

If that's not what you mean by "resolved" then clarify the question.

Registering a closed generic class implementation with an opened generic interface

You should register your repository as closed generic interface:

services.AddScoped<IConcreteRepository<Department, int>, DepartmentRepository>();


Related Topics



Leave a reply



Submit