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
A variable's declared type and the runtime type of its referenced object answer different questions. Animal determines which members the compiler exposes through value, while GetType reports the actual Cat instance created by new. Reflection represents that runtime information with a Type object. IsAssignableFrom must be read in the correct direction: the target type asks whether it can accept a value of the supplied source type. These are genuine metadata operations in the portable .NET 9 program, not a custom type-system model, and the external .NET 10 project executes the same bounded observations.
Trace the portable program
The object is a Cat even though the local is declared Animal, so the first line is runtime:Cat. Animal can accept that Cat source, producing base-accepts:True. A general Animal type does not establish that every instance is a Cat, so reversing the metadata question produces derived-accepts:False. No cast is performed and no object changes type during the checks. Trace the receiver and argument of each IsAssignableFrom call before running; a common wrong answer treats its English name as though the argument were the assignment target.
Execute the actual desktop lab
The desktop project executes the same Type queries under the stated .NET 10 runtime. Its matching output is useful behavioral evidence, but it does not mean a type name is a complete identifier across assemblies or load contexts. A Type carries identity information beyond Name, and two unrelated declarations can display the same simple text. For a registry, prefer deliberate keys and known factories or actual Type keys according to the contract. For diagnostics, a readable simple name can be enough. Choose the representation based on the question being asked rather than converting everything to strings and then reconstructing identity from those strings.
Pitfalls and boundary limits
Assignability also differs from object conversion and from a successful instantiation. An interface can be assignable from an implementing class without being directly constructible itself. Null has no runtime object on which to call GetType, while a declared Type value can still be inspected independently of any instance. Generic variance introduces further rules that should not be guessed from a family resemblance between type names. The sample intentionally uses ordinary inheritance so its direction is clear. Do not treat a reflection result as an authorization check or assume that discovering a compatible type makes invoking arbitrary constructors safe.
Practice one: isolate behavior
First reverse the two IsAssignableFrom operands in each baseline check and write the expected booleans before running. Then create a second concrete Animal subtype and compare it with Cat. Hint: sharing a base does not make two sibling classes assignable to one another. Keep the runtime instance and metadata questions separate by also evaluating the existing value with a normal type pattern. Add a null-reference scenario in its own guarded experiment and explain why testing null differs from inspecting typeof on a known declaration. These cases challenge direction, hierarchy, and instance presence independently.
Practice two: design and verify
For another exercise, design a small allowed-handler registry that accepts only types implementing a chosen capability. Define separate outcomes for incompatible metadata, missing construction policy, and unknown external key. Hint: IsAssignableFrom answers only the compatibility portion; it does not promise a usable constructor or a supported deployment path. Prefer explicit factories when a bounded known set fits the product. If actual plugin loading is required, identify the assembly-loading, trust, lifetime, and deployment constraints as separate requirements and test the real host rather than claiming this simple metadata check solved them.
Review the evidence
Review runtime-type reasoning by identifying the declared variable type, actual object type, assignment target, and source argument for each check. Verify concrete counterexamples such as sibling types and null without turning display names into identity. The portable program and desktop project prove the bounded observations they execute. They do not establish every generic conversion, every reflection permission, or the behavior of an untested AOT host. Keep compatibility, construction, invocation, and policy distinct so reflection supplies precise information to a design instead of becoming an opaque substitute for an explicit contract.
Runtime types and assignability direction — 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;
Animal value = new Cat();
Type runtime = value.GetType();
Console.WriteLine($"runtime:{runtime.Name}");
Console.WriteLine($"base-accepts:{typeof(Animal).IsAssignableFrom(runtime)}");
Console.WriteLine($"derived-accepts:{typeof(Cat).IsAssignableFrom(typeof(Animal))}");
class Animal { }
sealed class Cat : Animal { }
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: runtime:Cat base-accepts:True derived-accepts:False 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;
Animal value = new Cat();
Type runtime = value.GetType();
Console.WriteLine($"runtime:{runtime.Name}");
Console.WriteLine($"base-accepts:{typeof(Animal).IsAssignableFrom(runtime)}");
Console.WriteLine($"derived-accepts:{typeof(Cat).IsAssignableFrom(typeof(Animal))}");
class Animal { }
sealed class Cat : Animal { }
C# practice output
runtime:Cat base-accepts:True derived-accepts:False
Read IsAssignableFrom as target-accepts-source. Metadata inspection does not change the object or make the reverse assignment valid.
Further reading
Interview questions for this lesson
- Explain: How do a declared reference type, GetType(), and IsAssignableFrom answer different questions?
- Trace: Predict the three metadata lines and explain why the final compatibility check is false.
- Design: Design a bounded handler registry that validates compatibility without confusing it with construction or authorization.