Types (i.e., classes, value types, and interfaces) may be defined at the top level of a module:
The logical metadata table created by this declaration is specified in Partition II, section 21.34. RATIONALE For historical reasons, many of the syntactic classes used for defining types incorrectly use "class" instead of "type" in their name. All classes are types, but "types" is a broader term encompassing value types, and interfaces. 9.1 Type Header (<classHead>)A type header consists of
The extends keyword defines the base type of a type. A type shall extend from exactly one other type. If no type is specified, ilasm will add an extends clause to make the type inherit from System.Object. The implements keyword defines the interfaces of a type. By listing an interface here, a type declares that all of its concrete implementations will support the contract of that interface, including providing implementations of any virtual methods the interface declares. See also Partition II, sections 10 and 11. Example (informative): .class private auto autochar CounterTextBox extends [System.Windows.Forms]System.Windows.Forms.TextBox implements [.module Counter]CountDisplay { // body of the class } This code declares the class CounterTextBox, which extends the class System.Windows.Forms .TextBox in the assembly System.Windows.Forms and implements the interface CountDisplay in the module Counter of the current assembly. The attributes private, auto, and autochar are described in the following sections. A type can have any number of custom attributes attached. Custom attributes are attached as described in Partition II, section 20. The other (predefined) attributes of a type may be grouped into attributes that specify visibility, type layout information, type semantics information, inheritance rules, interoperation information, and information on special handling. The following subsections provide additional information on each group of predefined attributes.
9.1.1 Visibility and Accessibility Attributes
See Partition I [section 8.5.3]. A type that is not nested inside another shall have exactly one visibility (private or public) and shall not have an accessiblity. Nested types shall have no visibility, but instead shall have exactly one of the accessibility attributes (nested assembly, nested famandassem, nested family, nested famorassem, nested private, or nested public). The default visibility for top-level types is private. The default accessibility for nested types is nested private. 9.1.2 Type Layout Attributes
The type layout specifies how the fields of an instance of a type are arranged. A given type shall have only one layout attribute specified. By convention, ilasm supplies auto if no layout attribute is specified. auto: The layout shall be done by the CLI, with no user-supplied constraints. explicit: The layout of the fields is explicitly provided (see Partition II, section 9.7). sequential: The CLI shall lay out the fields in sequential order, based on the order of the fields in the logical metadata table (see Partition II, section 21.15). RATIONALE The default auto layout should provide the best layout for the platform on which the code is executing. sequential layout is intended to instruct the CLI to match layout rules commonly followed by languages like C and C++ on an individual platform, where this is possible while still guaranteeing verifiable layout. explicit layout allows the CIL generator to specify the precise layout semantics. 9.1.3 Type Semantics Attributes
The type semantic attributes specify whether an interface, class, or value type shall be defined. The interface attribute specifies an interface. If this attribute is not present and the definition extends (directly or indirectly) System.ValueType, a value type shall be defined (see Partition II, section 12). Otherwise, a class shall be defined (see Partition II, section 10). Note that the runtime size of a value type shall not exceed 1 MByte (0x100000 bytes)
9.1.4 Inheritance Attributes
Attributes that specify special semantics are abstract and sealed. These attributes may be used together. abstract specifies that this type shall not be instantiated. If a type contains abstract methods, the type shall be declared as an abstract type. sealed specifies that a type shall not have subclasses. All value types shall be sealed. RATIONALE Virtual methods of sealed types are effectively instance methods, since they cannot be overridden. Framework authors should use sealed classes sparingly, since they do not provide a convenient building block for user extensibility. Sealed classes may be necessary when the implementation of a set of virtual methods for a single class (typically inherited from different interfaces) becomes interdependent or depends critically on implementation details not visible to potential subclasses. A type that is both abstract and sealed should have only static members, and serves as what some languages call a namespace. 9.1.5 Interoperation Attributes
These attributes are for interoperation with unmanaged code. They specify the default behavior to be used when calling a method (static, instance, or virtual) on the class that has an argument or return type of System.String and does not itself specify marshalling behavior. Only one value shall be specified for any type, and the default value is ansi. ansi specifies that marshalling shall be to and from ANSI strings. unicode specifies that marshalling shall be to and from Unicode strings. autochar specifies either ANSI or Unicode behavior, depending on the platform on which the CLI is running.
9.1.6 Special Handling Attributes
These attributes may be combined in any way. beforefieldinit instructs the CLI that it need not initialize the type before a static method is called. See Partition II, section 9.5.3.
specialname indicates that the name of this item may have special significance to tools other than the CLI. See, for example, Partition I, sections 8.11.3 and 8.11.4. rtspecialname indicates that the name of this item has special significance to the CLI. There are no currently defined special type names; this is for future use. Any item marked rtspecialname shall also be marked specialname. RATIONALE If an item is treated specially by the CLI, then tools should also be made aware of that. The converse is not true. 9.2 Body of a Type DefinitionA type may contain any number of further declarations. The directives .event, .field, .method, and .property are used to declare members of a type. The directive .class inside a type declaration is used to create a nested type, which is discussed in further detail in Partition II, section 9.6.
9.3 Introducing and Overriding Virtual MethodsA virtual method of a base type is overridden by providing a direct implementation of the method (using a method definition; see Partition II, section 14.4) and not specifying it to be newslot (see Partition II, section 14.4.2.3). An existing method body may also be used to implement a given virtual declaration using the .override directive (see Partition II, section 9.3.2). 9.3.1 Introducing a Virtual MethodA virtual method is introduced in the inheritance hierarchy by defining a virtual method (see Partition II, section 14.4). The versioning semantics differ depending on whether or not the definition is marked as newslot (see Partition II, section 14.4.2.3): If the definition is marked newslot, then the definition always creates a new virtual method, even if a base class provides a matching virtual method. Any reference to the virtual method created before the new virtual function was defined will continue to refer to the original definition. If the definition is not marked newslot, then it creates a new virtual method only if there is no virtual method of the same name and signature inherited from a base class. If the inheritance hierarchy changes so that the definition matches an inherited virtual function, the definition will be treated as a new implementation of the inherited function.
9.3.2 The .override DirectiveThe .override directive specifies that a virtual method should be implemented (overridden), in this type, by a virtual method with a different name but with the same signature. It can be used to provide an implementation for a virtual method inherited from a base class or a virtual method specified in an interface implemented by this type. The .override directive specifies a Method Implementation (MethodImpl) in the metadata (see Partition II, section 14.1.4).
The first <typeSpec> :: <methodName> pair specifies the virtual method that is being overridden. It shall reference either an inherited virtual method or a virtual method on an interface that the current type implements. The remaining information specifies the virtual method that provides the implementation. While the syntax specified here and the actual metadata format (see Partition II, section 21.25) allows any virtual method to be used to provide an implementation, a conforming program shall provide a virtual method actually implemented directly on the type containing the .override directive. RATIONALE The metadata is designed to be more expressive than can be expected of all implementations of the VES. Example (informative): The following example shows a typical use of the .override directive. A method implementation is provided for a method declared in an interface (see Partition II, section 11). .class interface I { .method public virtual abstract void m() cil managed {} } .class C implements I { .method virtual public void m2() { // body of m2 } .override I::m with instance void C::m2() } The .override directive specifies that the C::m2 body shall provide the implementation or be used to implement I::m on objects of class C. 9.3.3 Accessibility and OverridingIf a type overrides an inherited method, it may widen, but it shall not narrow, the accessibility of that method. As a principle, if a client of a type is allowed to access a method of that type, then it should also be able to access that method (identified by name and signature) in any derived type. Table 3-1 specifies narrow and widen in this context a "Yes" denotes that the subclass can apply that accessibility, a "No" denotes it is illegal.
NOTE A method may be overridden even if it may not be accessed by the subclass. If a method has assembly accessibility, then it [the version in the subclass] shall have public accessibility if it is being overridden by a method in a different assembly. A similar rule applies to famandassem, where also famorassem is allowed outside the assembly. In both cases assembly or famandassem, respectively, may be used inside the same assembly. A special rule applies to famorassem, as shown in Table 3-1. This is the only case where the accessibility is apparently narrowed by the subclass. A famorassem method may be overridden with family accessibility by a type in another assembly. RATIONALE Because there is no way to specify "family or specific other assembly" it is not possible to specify that the accessibility should be unchanged. To avoid narrowing access, it would be necessary to specify an accessibility of public, which would force widening of access even when it is not desired. As a compromise, the minor narrowing of "family" alone is permitted. 9.4 Method Implementation RequirementsA type (concrete or abstract) may provide
A concrete (i.e., non-abstract) type shall provide, either directly or by inheritance, an implementation for
9.5 Special MembersThere are three special members, all methods, that can be defined as part of a type: instance constructors, instance finalizers, and type initializers. 9.5.1 Instance ConstructorsInstance constructors initialize an instance of a type. An instance constructor is called when an instance of a type is created by the newobj instruction (see Partition III [section 4.20]). Instance constructors shall be instance (not static or virtual) methods; they shall be named .ctor and marked both rtspecialname and specialname (see Partition II, section 14.4.2.6). Instance constructors may take parameters, but shall not return a value. Instance constructors may be overloaded (i.e., a type may have several instance constructors). Each instance constructor shall have a unique signature. Unlike other methods, instance constructors may write into fields of the type that are marked with the initonly attribute (see Partition II, section 15.1.2). Example (informative): The following shows the definition of an instance constructor that does not take any parameters: .class X { .method public rtspecialname specialname instance void .ctor() cil managed { .maxstack 1 // call super constructor ldarg.0 // load this pointer call instance void [mscorlib]System.Object::.ctor() // do other initialization work ret } }
9.5.2 Instance FinalizersThe behavior of finalizers is specified in Partition I, section 8.9.6.7. The finalize method for a particular type is specified by overriding the virtual method Finalize in System. Object. 9.5.3 Type InitializersTypes may contain special methods called type initializers to initialize the type itself. All types (classes, interfaces, and value types) may have a type initializer. This method shall be static, take no parameters, return no value, be marked with rtspecialname and specialname (see Partition II, section 14.4.2.6), and be named .cctor. Like instance initializers, type initializers may write into static fields of their type that are marked with the initonly attribute (see Partition II, section 15.1.2). NOTE Type initializers are often simple methods that initialize the type's static fields from stored constants or via simple computations. There are, however, no limitations on what code is permitted in a type initializer. 9.5.3.1 Type Initialization GuaranteesThe CLI shall provide the following guarantees regarding type initialization (but see also Partition II, sections 9.5.3.2 and 9.5.3.3):
9.5.3.2 Relaxed GuaranteesA type can be marked with the attribute beforefieldinit (see Partition II, section 9.1.6) to indicate that all the guarantees specified in Partition II, section 9.5.3.1 are not required. In particular, the final requirement of guarantee 1 need not be provided: the type initializer need not run before a static method is called or referenced. RATIONALE When code can be executed in multiple application domains, it becomes particularly expensive to ensure this final guarantee. At the same time, examination of large bodies of managed code have shown that this final guarantee is rarely required, since type initializers are almost always simple methods for initializing static fields. Leaving it up to the CIL generator (and hence, possibly, to the programmer) to decide whether this guarantee is required therefore provides efficiency when it is desired at the cost of consistency guarantees. 9.5.3.3 Races and DeadlocksIn addition to the type initialization guarantees specified in Partition II, section 9.5.3.1, the CLI shall ensure two further guarantees for code that is called from a type initializer:
RATIONALE Consider the following two class definitions: .class public A extends [mscorlib]System.Object { .field static public class A a .field static public class B b .method public static rtspecialname specialname void .cctor () { ldnull // b=null stsfld class B A::b ldsfld class A B::a // a=B.a stsfld class A A::a ret } } .class public B extends [mscorlib]System.Object { .field static public class A a .field static public class B b .method public static rtspecialname specialname void .cctor () { ldnull // a=null stsfld class A B::a ldsfld class B A::b // b=A.b stfld class B B::b ret } } After loading these two classes, an attempt to reference any of the static fields causes a problem, since the type initializer for each of A and B requires that the type initializer of the other be invoked first. Requiring that no access to a type be permitted until Its initializer has completed would create a deadlock situation. Instead, the CLI provides a weaker guarantee: the initializer will have started to run, but it need not have completed. But this alone would allow the full uninitialized state of a type to be visible, which would make it difficult to guarantee repeatable results. There are similar, but more complex, problems when type initialization takes place in a multi-threaded system. In these cases, for example, two separate threads might start attempting to access static variables of separate types (A and B), and then each would have to wait for the other to complete initialization. A rough outline of the algorithm is as follows: 1. At class load time (hence prior to initialization time), store zero or null into all static fields of the type. 2. If the type is initialized, you are done. 2.1. If the type is not yet initialized, try to take an initialization lock. 2.2. If successful, record this thread as responsible for initializing the type and proceed to step 2.3. 2.2.1. If not, see whether this thread or any thread waiting for this thread to complete already holds the lock. 2.2.2. If so, return, since blocking would create a deadlock. This thread will now see an incompletely initialized state for the type, but no deadlock will arise. 2.2.3. If not, block until the type is initialized then return. 2.3 Initialize the parent type and then all interfaces implemented by this type. 2.4 Execute the type initialization code for this type. 2.5 Mark the type as initialized, release the initialization lock, awaken any threads waiting for this type to be initialized, and return. 9.6 Nested TypesNested types are specified in Partition I, section 8.5.3.4. Interfaces may be nested inside of classes and value types, but classes and value types shall not be nested inside of interfaces. For information about the logical tables associated with nested types, see Partition II, section 21.29. NOTE A nested type is not associated with an instance of its enclosing type. The nested type has its own base type and may be instantiated independent of the enclosing type. This means that the instance members of the enclosing type are not accessible using the this pointer of the nested type. A nested type may access any members of its enclosing type, including private members, as long as the member is static or the nested type has a reference to an instance of the enclosing type. Thus, by using nested types a type may give access to its private members to another type. On the other side, the enclosing type may not access any private or family members of the nested type. Only members with assembly, famorassem, or public accessibility can be accessed by the enclosing type. Example (informative): The following example shows a class declared inside another class. Both classes declare a field. The nested class may access both fields, while the enclosing class does not have access to the field b. .class private auto autochar CounterTextBox extends [System.Windows.Forms]System.Windows.Forms.TextBox implements [.module Counter]IcountDisplay { .field static private int32 a /* Nested class. Declares the NegativeNumberException */ .class nested assembly NonPositiveNumberException extends [mscorlib]System.Exception { .field static private int32 b // body of nested class } // end of nested class NegativeNumberException }
9.7 Controlling Instance LayoutThe CLI supports both sequential and explicit layout control (see Partition II, section 9.1.2). For explicit layout it is also necessary to specify the precise layout of an instance (see also Partition II, sections 21.18 and 21.16).
The optional int32 specified in brackets at the beginning of the declaration specifies the byte offset from the beginning of the instance of the type. This form of explicit layout control shall not be used with global fields specified using the at notation (see Partition II, section 15.3.2). Offset values shall be 0 or greater; they cannot be negative. It is possible to overlap fields in this way, even though it is not recommended. The field may be accessed using pointer arithmetic and ldind to load the field indirectly or stind to store the field indirectly (see Partition III [sections 3.42 and 3.62.]) See Partition II, sections 21.18 and 21.16 for encoding of this information. For explicit layout, every field shall be assigned an offset. The .pack directive specifies that fields should be placed within the runtime object at addresses which are a multiple of the specified number, or at natural alignment for that field type, whichever is smaller. For example, .pack 2 would allow 32-bit-wide fields to be started on even addresses whereas without any .pack directive, they would be naturally aligned that is to say, placed on addresses that are a multiple of 4. The integer following .pack shall be one of 0, 1, 2, 4, 8, 16, 32, 64 or 128. (A value of zero indicates that the pack size used should match the default for the current platform). The .pack directive shall not be supplied for any type with explicit layout control. The directive .size specifies that a memory block of the specified amount of bytes shall be allocated for an instance of the type. For example, .size 32 would create a block of 32 bytes for the instance. The value specified shall be greater than or equal to the calculated size of the class, based upon its field sizes and any .pack directive. Note that if this directive applies to a value type, then the size shall be less than 1 MByte. NOTE Metadata that controls instance layout is not a "hint," it is an integral part of the VES that shall be supported by all conforming implementations of the CLI.
Example (informative): The following class uses sequential layout of its fields: .class sequential public SequentialClass { .field public int32 a // store at offset 0 bytes .field public int32 b // store at offset 4 bytes } The following class uses explicit layout of its fields: .class explicit public ExplicitClass { .field [0] public int32 a // store at offset 0 bytes .field [6] public int32 b // store at offset 6 bytes } The following value type uses .pack to pack its fields together: .class value sealed public MyClass extends [mscorlib]System.ValueType{ .pack 2 .field public int8 a // store at offset 0 bytes .field public int32 b // store at offset 2 bytes (not 4) } The following class specifies a contiguous block of 16 bytes: .class public BlobClass { .size 16 } 9.8 Global Fields and MethodsIn addition to types with static members, many languages have the notion of data and methods that are not part of a type at all. These are referred to as global fields and methods. It is simplest to understand global fields and methods in the CLI by imagining that they are simply members of an invisible abstract public class. In fact, the CLI defines such a special class, named "<Module>", that does not have a base type and does not implement any interfaces. The only noticeable difference is in how definitions of this special class are treated when multiple modules are combined together, as is done by a class loader. This process is known as metadata merging. For an ordinary type, if the metadata merges two definitions of the same type, it simply discards one definition on the assumption they are equivalent and that any anomaly will be discovered when the type is used. For the special class that holds global members, however, members are unioned across all modules at merge time. If the same name appears to be defined for cross-module use in multiple modules, then there is an error. In detail:
|