01 · TYPE IDENTITY
Start with a value or a type.
Suppose a diagnostic tool wants to describe the types and members available to a program. It needs metadata, without running the methods it discovers. Introspection is that descriptive layer; invoking code is a separate, future capability.
The example declares an empty Widget class. Its instance is held through Object, but GetType() still describes the concrete allocation. typeof(Widget) describes the declared type. Both return the same public contract: System.Introspection.TypeInfo.
let widget: Object = Widget()
let description = widget.GetType()
if description.Equals(typeof(Widget)) {
Console.WriteLine("Instance and declared type agree")
}
Use Equals to compare type identity. Names are useful for display, but do not uniquely identify types across modules. There is no public System.Type class or intermediate .Info property in this Raven profile. Calling GetType() on null faults.
02 · ASSEMBLIES AND MODULES
Let RuntimeContext locate the assembly.
RuntimeContext.Current describes the current loaded program. ExecutingAssembly identifies the assembly of the source caller through the runtime facade. A call made from a dependency reports that dependency, rather than always reporting the entry assembly.
let assembly = RuntimeContext.Current.ExecutingAssembly
Console.WriteLine(assembly.Name)
for reference in assembly.ReferencedAssemblies {
Console.WriteLine(reference.Name)
}
for module in assembly.GetModules() {
Console.WriteLine(module.Name)
for discovered in module.GetTypes() {
Console.WriteLine(discovered.Name)
}
}
In the saved Demo project, the executing assembly references System.Runtime. That is the foundation’s logical identity; compiler bootstrap names are not additional platform dependencies. References are direct edges, not a recursive dependency listing.
AssemblyInfo describes an assembly; ModuleInfo describes a module within it. Both expose GetTypes(). The current implementation lists retained, loaded definitions, including nonpublic types—not every type from an original source assembly that the importer may have discarded.
03 · DEFINITION IDENTITY
A metadata token needs its module.
if description.MetadataToken == typeof(Widget).MetadataToken {
Console.WriteLine("Same definition token")
}
Console.WriteLine(description.Module.Name)
The token agrees here because both descriptions refer to Widget in the same module. A token alone is not a global identifier. Type, member and parameter interfaces expose Module alongside MetadataToken; the assembly and module interfaces expose their own tokens too.
Source definition tokens are preserved where the importer retains them. Merged runtime definitions receive module-scoped tokens. They are stable within an artifact, not a persistence key across rebuilds. Constructed generic types share their definition’s token. Arrays, pointer/by-reference wrappers and generic-parameter placeholders currently return zero; an absent parameter row also has token zero.
04 · COLLECTION CAPABILITIES
Count, index and enumerate.
let methods: Sequence<MethodInfo> = typeof(int).GetMethods()
Console.WriteLine(methods.Count)
Console.WriteLine(MemberKind(methods[0]))
for parameter in methods[0].GetParameters() {
Console.WriteLine(parameter.Name)
}
Every public collection-returning Introspection method now uses Sequence<T>. That includes types, members, parameters, generic arguments, interfaces and enum names. The sample uses Count, an indexer and a for loop; Iterable-based query extensions also work.
The interface has no collection mutation members. The current implementation returns independent snapshots, but the interface alone does not promise immutable concrete storage. Sequence is invariant: a Sequence<MethodInfo> is not implicitly a Sequence<MemberInfo>; individual methods can still be passed as MemberInfo.
Sequence<Element> and Length with Count. Rebuild against a matching reference and runtime library. Array assignment and indexer writes through the Sequence contract are rejected.05 · A SEALED MODEL
Handle each kind of member.
The public Info contracts are sealed interfaces. The current MemberInfo hierarchy has four public cases, so Raven can check that this match covers the whole hierarchy.
func MemberKind(member: MemberInfo) -> string {
return match member {
FieldInfo => "Field"
MethodInfo => "Method"
PropertyInfo => "Property"
TypeInfo => "Type"
}
}
Callers work with those public cases, without matching private runtime implementation classes. TypeInfo is a member case, so a nested type can be described as a member. DeclaringType returns Option<TypeInfo>: nested types and ordinary members have an owner; top-level types do not. ParameterInfo remains separate.
let flags = BindingFlags.Instance | BindingFlags.NonPublic
let fieldInfo = typeof(Date).GetFields(flags)[0]
let property = typeof(Date).GetProperties()[0]
Console.WriteLine(MemberKind(fieldInfo))
Console.WriteLine(MemberKind(property))
Console.WriteLine(property.GetIndexParameters().Count)
BindingFlags is an ordinary enum. Here its combined flags include nonpublic instance fields of Date. Metadata visibility does not grant access to read those fields or invoke private methods. Describing a property does not execute its accessor.
RUN IT YOURSELF
One complete program.
Use the matching Preview 8 compiler, runtime and reference library. Follow the project-based setup guide, then save this sample as Main.rvn and run the neoCLR task.
- Download the complete Raven sample, including its imports, Widget class, helper and Main function.
- Copy it into
Main.rvnin the preparedDemoproject and save. - Choose Terminal → Run Task → neoCLR: Run saved project. The task compiles, imports, verifies and runs the saved program on neoCLR.
Raven’s ordinary Run/Debug commands target .NET; use the neoCLR task for this walkthrough. For the project named Demo and the current library snapshot, the expected output is:
Instance and declared type agree
Demo
System.Runtime
Demo
Widget
Same definition token
Demo
5
Method
value
Field
Property
0
The method count and parameter name reflect this preview’s metadata inventory, not a promise that later releases keep the same members or ordering. The snippets and expected output come from the same files used by the saved-project check.
DESIGN CHOICES
Familiar terms, explicit boundaries.
The .NET comparison informs this API’s ergonomics: assembly/module descriptions, member queries, BindingFlags and module-scoped metadata tokens are familiar concepts. neoCLR places ambient discovery on RuntimeContext and returns one TypeInfo model directly from both forms of type acquisition.
.NET’s Assembly.GetReferencedAssemblies() returns assembly-name identities. This preview instead returns resolved AssemblyInfo descriptions, making traversal convenient but requiring references to exist in the loaded catalog. An unavailable reference faults explicitly; it is neither hidden nor loaded from disk.
Sequence states the collection capability without requiring an array in the public contract. It permits future storage changes, at the cost of a deliberate API break for existing array-oriented callers. These choices aim for a coherent small API; they do not promise .NET binary compatibility or identical behavior in every edge case.
Read the design record and primary .NET comparisons →PREVIEW BOUNDARY
Discovery is the current scope.
- There is one loaded-program context. Dynamic assembly loading and resolution belong to future RuntimeContext work.
- Queries cover retained metadata. Application property projection and generic method-definition reflection remain limited.
- Open generic definitions can report identity, shape, arguments, tokens and module. Their member, base-type and interface queries require a closed type and fault otherwise.
- Dynamic invocation, emit and offline metadata contexts remain future work. TypeInfo is part of the sealed MemberInfo hierarchy.
The Introspection story closes at this boundary for now. The next slice is described in the Strings and UTF-8 guide.
PROPOSED DIRECTION
Where we’re heading.
The aim is one descriptive model that can later support reflection and emit. Dynamic assembly loading belongs with RuntimeContext; offline metadata could use a different resolution context. These are directions to explore, not implemented APIs or release commitments. Identity, resolution and lifetime rules need further work.
See the proposals and their tradeoffs →HELP SHAPE THE NEXT PREVIEW
What would you try next?
- Does RuntimeContext make the ownership of discovery clear?
- Do Sequence results provide enough capability for your metadata tooling?
- Would your application need to inspect unresolved reference identities?
- Which concrete use case is blocked by the current discovery limits?
Share the scenario, the sample you tried and the behavior you expected. Questions and criticism are welcome.
Share feedback on GitHub