Introduction to MSIL – Part 3 – Defining Types
In this installment of the MSIL series, I describe how types are defined.
Here is a minimal reference type called House. As I write this, we are looking for a house in British Columbia, so this was the first thing that came to mind.
.class Kerr.RealEstate.House
{
.method public void .ctor()
{
.maxstack 1
ldarg.0 // push "this" instance onto the stack
call instance void [mscorlib]System.Object::.ctor()
ret
}
}
This is a very simple type. Note that you must declare a constructor for a concrete reference type. Unlike languages like C# and C++, the IL assembler will not generate a constructor for you automatically.
Types are defined using the .class directive followed by a type header. The class keyword is used instead of the more intuitive type for historical reasons. When you read class in MSIL source code, just think type. The type header consists of a number of type attributes followed by the name of the type you are defining. To define the equivalent of a C# static class in MSIL you can write the following.
.class abstract sealed Kerr.RealEstate.MortgageCalculator
{
/* members */
}
abstract and sealed are the type attributes. An abstract type cannot be instantiated and a sealed type cannot have sub-types. There are attributes to control visibility, such as public and private. There are attributes to control field layout, such as auto and sequential. For a complete list of attributes please consult the CLI specification. Many attributes are applied automatically, which can save you a lot of typing. Fortunately these defaults are quite intuitive so you should become familiar with them quickly. As an example, extending the System.ValueType from the mscorlib assembly defines a value type. Since the CLI requires that value types be sealed, the IL assembler will automatically add this attribute for you.
The name of the type in the example above is Kerr.RealEstate.MortgageCalculator. The CLI does not recognize namespaces as a distinct concept. Rather the full type name is always used. The syntax shown above is only supported by the IL Assembler that ships with version 2.0 of the .NET Framework. If you are working with version 1.x then you need to group your namespace members with a .namespace directive, as shown below. Note that this syntax is also supported in version 2.0.
.namespace Kerr.RealEstate
{
.class abstract sealed MortgageCalculator
{
/* members */
}
}
Following the type name you have the option of specifying the base type. The extend keyword is used for this purpose. If no base type is specified, the IL assembler will add the extend clause to make the type inherit from the System.Object type from the mscorlib assembly, resulting in a reference type. And finally, the type header can provide a list of interfaces that the type and its descendants will implement and satisfy, becoming the interfaces of the type.
.class Kerr.RealEstate.RoomList
extends [System.Windows.Forms]System.Windows.Forms.ListView
implements Kerr.IView
{
/* members */
}
In this example, the Kerr.RealEstate.RoomList type has System.Windows.Forms.ListView, defined in the System.Windows.Forms assembly, as its base type. Keep in mind that the CLI requires that every user-defined type extend exactly one other type. The RoomList type also implements the Kerr.IView interface type.
With this basic introduction to type definitions, you should now be able to start defining more interesting types. To define an interface, simply use the interface attribute in your type header. If you want a value type, known as a struct in C#, simply extend the System.ValueType type from the mscorlib assembly. Now you should be able to see why the .class directive is perhaps not the best name for it.
.class interface Kerr.IView
{
/* members */
}
.class Kerr.RealEstate.HouseData
extends [mscorlib]System.ValueType
{
/* members */
}
Read part 4 now: Defining Type Members
© 2004 Kenny Kerr