Another form of runtime information available in D is a list of all classes in the program. Using this information, we can achieve tasks that will be impossible at runtime, such as getting a list of all child classes of a particular class. A potential use of this capability will be to load game objects, defined as classes inheriting a common base, from an XML file at runtime.
Let's execute the following steps to get a list of child classes:
typeid
of the parent class you're interested in.ModuleInfo
with the foreach
loop.localClasses
member of each loop value.The code is as follows:
ClassInfo[] getChildClasses(ClassInfo c) { ClassInfo[] info; foreach(mod; ModuleInfo) { foreach(cla; mod.localClasses) { if(cla.base is c) info ~= cla; } } return info; } class A {} class B : A {} class C : A {} void main() { foreach(cla; getChildClasses(A.classinfo)) { import std.stdio; writeln(cla.name); // you could also create the class with cla.create(); } }
Running the program will give the following output:
test.B test.C
Notice that test
is the name of the module containing this program. Also, try adding more modules to inherit from class A and observe that they still work.
Similar to TypeInfo
, the D compiler also automatically creates static instances of a type called ModuleInfo
, tailored to the needs of druntime, which provides limited runtime reflection across the entire program. The definition is in object.d
, so no explicit import is required to use it.
The members of ModuleInfo
include pointers to the module constructor, destructor, unit tests, a list of imported modules, a list of local classes, and the module name. It also has a static opApply
function that lets us perform a foreach
loop over it.
The primary use of ModuleInfo
is for the internal druntime code. The druntime is responsible for calling your main
function. Before it runs your function, it loops over all modules in the application to run their module constructors, using the list of imported modules to run them in the proper order. So, dependencies are initialized before the modules that use them. If unit tests are enabled, druntime also runs those tests through ModuleInfo
before running main
.
A secondary use of ModuleInfo
is to find and inspect classes present in the program. This is how the Object.factory
function is implemented, and it is through these facilities that we achieved our task to find all the child classes.
Getting a list of all the available child classes is impossible at compile time. While a list of base classes is possible, the compiler must be aware of all base classes to form a working inheritance hierarchy—a list of child classes is a runtime task because two modules or shared libraries may both provide child classes and may be compiled separately, with no knowledge of one another.
The foreach(mod; ModuleInfo)
loops over each module in the program, yielding ModuleInfo*
on each iteration. Then, for each module, we loop over the localClasses
member. The localClasses
array is an array of the TypeInfo_Class
objects (sometimes known by its alias, ClassInfo
), the same type we retrieved with typeid
in the previous recipe. The base member of this object also points to a TypeInfo_Class
instance, and it may be null
in the case of Object
, which is the root of all D classes.
By comparing these TypeInfo
instances with the is
identity operator, we will build a list of each type we're interested in. It can be used with further runtime reflection operations, including to call the create method to construct a default object of that class type.
It is impossible to declare a variable, cast to, or to use any other compile-time operation on a ClassInfo
object to learn more about or manipulate the class because compile time is longer than the time we're in this function. The ClassInfo
objects aren't special in any sense other than the fact that they are automatically created at runtime; they are just another block of data. We'll cover a technique to enable extended runtime reflection later in this chapter.
3.15.144.56