Skip to content

New metadata construct: Metaclasses #4247

@masonwheeler

Description

@masonwheeler

In #4211, @jbevain brought up the possibility that:

What would be interesting is to tag all proposals that require new IL or metadata constructs and when there's enough of them see about pushing for a new revision. (The only change so far was for generics in 2.0).

One thing that's very noticeably missing from the CLR object model is metaclasses. Coming from a Delphi background, where metaclasses (aka TClass) are used all over, it's quite jarring to find them completely missing.

Basic Overview:

The Delphi object model is quite similar to the CLR object model in many ways--this is not surprising, as both were designed by the same architect--but one major feature that's missing is the metaclass. Where System.Object contains a GetType method that returns a Type object, Delphi has something very similar, except the TClass value that it returns is far more powerful, bringing several features that both the CLR and JVM object models lack.

A metaclass is a fundamentally new kind of primitive value, like ints, objects, and delegates, that represents the class itself, not a particular instance of the class. They are immutable, compiler-generated singletons, one for each class in the project, much like Type objects.

Metaclass values and inheritance

In the CLR, it's possible to declare a variable of type Type and assign Type objects to it, but it's not possible to restrict the allowable types beyond this. Delphi's metaclasses take this a step further: a metaclass variable can be declared as type TClass (the metaclass of the base object class) or as a more restricted metaclass of a specific class. In the latter case, only a metaclass value of that class or a class derived from it can be assigned to this variable. This is statically checked by the compiler in exactly the same way that object instance types are checked.

This is important because while a Type only contains reflection data for the class it represents, and each Type object gives access to the exact same set of methods and properties, a metaclass has class-scope methods and properties on it much like a class does, including virtual methods.

Virtual class methods and constructors

In the CLR, all methods are either instance-scope methods or static methods. With metaclasses, a third option is available: class-scope methods. Their this parameter is the metaclass value, not an object instance. Class-scope methods can be declared virtual, which means exactly what it intuitively sounds like: virtual class-scope methods may be overridden in derived classes, and when invoking a virtual class-scope method, the method that is called depends on the metaclass it is being invoked upon.

The most common and arguably most useful application of this feature is the use of virtual constructors. Constructors are considered class-scope methods, and can be declared virtual just like any other method. This makes the Factory Pattern trivial: instantiating an object from a defined hierarchy whose class is not known until runtime is a simple matter of registering the class with a map that resolves to a metaclass, and then performing a key lookup and calling a virtual constructor on the metaclass.

Delphi's GUI library has been using this technique to good advantage for 20 years now. The equivalent on the CLR requires the use of Activator.CreateInstance, which has a much higher overhead as it must instantiate everything via reflection and the parameters passed cannot be statically verified at compile time. A virtual constructor call, by contrast, has all the type safety benefits of any other virtual method call.

Plug-in functionality

Virtual constructor calls are useful for object factories, but also for a kind of "inverse factory" construct. Consider the scenario in which a factory needs to produce Widget objects. 90% of the time, it needs to produce a FrobWidget, but in certain cases, it needs to produce a BozWidget instead, and the code that actually creates the widgets is several steps removed from the code that makes the decision as to what type is necessary. This could get messy very quickly, but with metaclasses it can be greatly simplified:

class WidgetGenerator {
    public Metaclass<Widget> WidgetType {get; set;}

    public Widget Generate(Widget owner) {
       Widget result = new WidgetType(owner);
    }
}

Now the decision-making code only needs a reference to the same WidgetGenerator that the generating code uses, and when appropriate, it could do something like this:

if (needBoz) {
   this.Generator.WidgetType = BozWidget;
}
try
{
   //Generator gets used 10 steps down the call stack from here!
   DoSomethingComplicated();
} finally {
   this.Generator.WidgetType = FrobWidget;
}

Class constructors

When metaclasses are available, static constructors on a class become class constructors instead, receiving a metaclass as a this argument and behaving as class-scope methods. It is not valid to call an base class's constructor from a derived class constructor, to avoid having the same base class constructor being called multiple times.

Lexical scope

Within a class-scope method, any static or class-scope member on that class or any base class is considered to be in scope, (unless visibility specifiers on a base class make it not visible, of course,) and can be accessed by its member name alone, without requiring a class name prefix.

Summary

It's difficult to explain 20 years of acquired knowledge about the usefulness of a language feature in a document meant to be read in 10 minutes, but this is what I've attempted to do. Obviously, implementing metaclasses in the CLR would be a decidedly non-trivial project requiring changes to the IL and metadata specifications, but its advantages are well-known to those familiar with metaclasses and the idioms they enable, and having them available would greatly enhance the CLR and any languages that implement this functionality. It's definitely a feature worth considering for inclusion in the medium-to-long term.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-Metadesign-discussionOngoing discussion about design without consensusuntriagedNew issue has not been triaged by the area owner

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions