Read or download the complete SDK project, then follow its setup on your computer. The separate C# companion practice below runs in SharpRune’s .NET 9 console environment.
The runtime contract
Generic reflection distinguishes a definition with unbound parameters from a constructed type whose arguments have been selected. Dictionary with string keys and integer values is a closed construction; Dictionary with empty argument positions identifies the reusable generic definition. Both are Type metadata, but only a suitable closed concrete construction can describe an object you could instantiate. The portable program inspects an already known construction and does not call MakeGenericType on arbitrary user input. That narrower choice avoids confusing metadata inspection with the code-generation and reachability requirements that matter in trimmed or NativeAOT deployment.
Trace the portable program
The selected dictionary reports IsConstructedGenericType true. Its generic definition equals the unbound Dictionary definition, so the second observation is also true. GetGenericArguments returns the arguments in their declared positions, producing String followed by Int32. The definition still contains generic parameters, so the final open observation is true. Output is constructed:True, definition:True, arguments:String,Int32, and open:True. Reading the argument positions matters: swapping key and value creates a different closed type even though both constructions refer back to the same generic definition.
Execute the actual desktop lab
The desktop project intentionally inspects the already known Dictionary construction instead of generating arbitrary combinations at runtime. A constructed generic Type can be closed or can still contain parameters in more complex reflected contexts, so read the relevant properties rather than equating every occurrence of IsGenericType with a usable concrete object. GetGenericTypeDefinition identifies the reusable declaration, and the positional arguments identify this construction. If a library needs to match an implemented generic capability, inspect the actual interfaces and their definitions deliberately. Comparing an open definition directly with a closed type's simple name does not express that requirement.
Pitfalls and boundary limits
MakeGenericType is a further operation with validation and deployment implications. The supplied arguments must satisfy arity and constraints, and an open definition alone cannot be instantiated. NativeAOT also needs code and metadata reachability for supported constructions, so successful desktop reflection does not prove every runtime-selected instantiation is available after native publication. The sample avoids that leap. It also avoids asserting a particular assembly-qualified textual encoding as a stable wire format. Type identity, generic declaration identity, and a serialization protocol's type discriminator require different levels of completeness and stability.
Practice one: isolate behavior
First replace Dictionary of string and integer with Dictionary of integer and string. Predict the reversed argument-name list while the constructed, definition, and open booleans retain their original meanings. Hint: compare the two closed Type objects directly as well as their common generic definition. Then inspect a nongeneric type and decide which generic-only operation must be guarded before calling it. Keep the normal and exceptional cases separate so an invalid GetGenericTypeDefinition call cannot stop the observations that demonstrate the valid dictionary construction.
Practice two: design and verify
For another exercise, add a small generic class with a documented constraint and inspect that constraint through its definition. In a separate desktop experiment, attempt a construction that violates it and record the actual diagnostic or exception boundary. Hint: a runtime Type argument is not exempt from the generic contract simply because source syntax was bypassed. If the intended application supports only a fixed set of constructions, compare an explicit registration design with unrestricted MakeGenericType. State what a real NativeAOT publication would need to verify before selecting the dynamic design.
Review the evidence
Review generic metadata by naming the definition, selected arguments, remaining parameters, constraints, and intended construction or invocation step. The four baseline lines establish a precise dictionary example and are not a promise about every possible reflected generic shape. The external run adds actual desktop execution but does not generate a native artifact. Keep known closed types, open templates, and arbitrary runtime synthesis distinct. That separation makes diagnostics and registries easier to reason about and prevents a convenient reflection API from silently becoming a deployment requirement the application has never tested. Constraint metadata can describe reference-type requirements, constructor requirements, and other type relationships independently. Inspect those declared constraints instead of inferring them from a parameter name such as TEntity or from one successful closed construction.
Open definitions and constructed generic types — desktop runtime lab
Set up the SDK project
- Save both files at their exact paths in a fresh owned directory.
- Use a .NET 10 SDK, then run dotnet run --project RuntimeLab.csproj. This project intentionally uses C# 13 syntax.
- Workers and waits are bounded; a timeout or a violated invariant fails the process. No benchmark or NativeAOT publication is implied by an ordinary run.
Download complete SDK project ↗
Project files
Program.cs
Download file ↗using System;
using System.Linq;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Reflection;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
Type closed = typeof(Dictionary<string, int>);
Type definition = closed.GetGenericTypeDefinition();
Console.WriteLine($"constructed:{closed.IsConstructedGenericType}");
Console.WriteLine($"definition:{definition == typeof(Dictionary<,>)}");
Console.WriteLine($"arguments:{string.Join(",", closed.GetGenericArguments().Select(type => type.Name))}");
Console.WriteLine($"open:{definition.ContainsGenericParameters}");
RuntimeLab.csproj
Download file ↗<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>13.0</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PublishAot>false</PublishAot>
</PropertyGroup>
</Project>
Expected result
Exact behavioral stdout on the stated desktop lab: constructed:True definition:True arguments:String,Int32 open:True Variable diagnostics, where emitted, go to stderr and must not be treated as portable timing or allocation guarantees.
Use the listed SDK and dependencies for this project. Open C# practice runs the separate portable exercise.
C# companion practice
using System;
using System.Linq;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Reflection;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
Type closed = typeof(Dictionary<string, int>);
Type definition = closed.GetGenericTypeDefinition();
Console.WriteLine($"constructed:{closed.IsConstructedGenericType}");
Console.WriteLine($"definition:{definition == typeof(Dictionary<,>)}");
Console.WriteLine($"arguments:{string.Join(",", closed.GetGenericArguments().Select(type => type.Name))}");
Console.WriteLine($"open:{definition.ContainsGenericParameters}");
C# practice output
constructed:True definition:True arguments:String,Int32 open:True
Compare two closed constructions and their shared definition. An open generic Type is metadata, not an instantiable object type.
Further reading
Interview questions for this lesson
- Explain: What is the difference between a generic definition and a constructed Type, and why is an open definition not a ready-to-create object?
- Trace: Trace all four generic-metadata lines, including the argument ordering.
- Design: How would you implement a generic capability matcher without using type names as wildcard patterns?