9 Defining Types


Types (i.e., classes, value types, and interfaces) may be defined at the top level of a module:

<decl> ::=

Section in Partition II

 

.class <classHead> { <classMember>* }

5.10

| ...

 

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

  • Any number of type attributes

  • A name (an <id>)

  • A base type (or parent type), which defaults to System.Object

  • An optional list of interfaces whose contract this type and all its descendant types shall satisfy

<classHead> ::=

 

<classAttr>* <id> [extends <typeReference>] [implements <typeReference> [, <typeReference>]*]

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 graphics/ccc.gif.TextBox in the assembly System.Windows.Forms and implements the interface CountDisplay in graphics/ccc.gif the module Counter of the current assembly. The attributes private, auto, and autochar are graphics/ccc.gif 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.

<classAttr> ::=

Description

Section in Partition II

 

abstract

Type is abstract.

9.1.4

| ansi

Marshal strings to platform as ANSI.

9.1.5

| auto

Auto layout of type.

9.1.2

| autochar

Marshal strings to platform based on platform.

9.1.5

| beforefieldinit

Calling static methods does not initialize type.

9.1.6

| explicit

Layout of fields is provided explicitly.

9.1.2

| interface

Interface declaration.

9.1.3

| nested assembly

Assembly accessibility for nested type.

9.1.1

| nested famandassem

Family-and-assembly accessibility for nested type.

9.1.1

| nested family

Family accessibility for nested type.

9.1.1

| nested famorassem

Family-or-assembly accessibility for nested type.

9.1.1

| nested private

Private accessibility for nested type.

9.1.1

| nested public

Public accessibility for nested type.

9.1.1

| private

Private visibility of top-level type.

9.1.1

| public

Public visibility of top-level type.

9.1.1

| rtspecialname

Special treatment by runtime.

9.1.6

| sealed

The type cannot be subclassed.

9.1.4

| sequential

The type is laid out sequentially.

9.1.2

| serializable

Type may be serialized.

9.1.6

| specialname

Special treatment by tools.

9.1.6

| unicode

Marshal strings to platform as Unicode.

9.1.5

ANNOTATION

Implementation-Specific (Microsoft): The above grammar also includes

 
 <classAttr> ::= import 

to indicate that the type is imported from a COM type library.


9.1.1 Visibility and Accessibility Attributes

<classAttr> ::= ...

| nested assembly

| nested famandassem

| nested family

| nested famorassem

| nested private

| nested public

| private

| public

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

<classAttr> ::= ...

| auto

| explicit

| sequential

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

<classAttr> ::= ...

| interface

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)

ANNOTATION

Implementation-Specific (Microsoft): The current implementation allows 0x3F0000 bytes, but this size may be reduced in future.


9.1.4 Inheritance Attributes

<classAttr> ::= ...

| abstract

| sealed

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

<classAttr> ::= ...

| ansi

| autochar

| unicode

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.

ANNOTATION

Although in the managed world there is only one type of string, in the unmanaged world there are two standards ANSI and Unicode so it is important to specify what the native code uses as the string standard. Typically, this specification is done per class, but it can be done per parameter.


9.1.6 Special Handling Attributes

<classAttr> ::= ...

| beforefieldinit

| serializable

| specialname

| rtspecialname

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.

ANNOTATION

Implementation-Specific (Microsoft): The serializable bit is in the file format and so is listed here, but its use is not standardized. Its presence does allow an implementation of serialization, enabling remoting, interactive Web services, saving data in persistent format, etc. In the Microsoft CLI implementation the Common Language Runtime serializable indicates that the fields of the type may be serialized into a data stream by the CLR serializer.


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 Definition

A 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.

<classMember> ::=

Description

Section in Partition II

 

.class <classHead> { <classMember>* }

Defines a nested type.

9

| .custom <customDecl>

Custom attribute.

20

| .data <datadecl>

Defines static data associated with the type.

15.3

| .event <eventHead> { <eventMember>* }

Declares an event.

17

| .field <fieldDecl>

Declares a field belonging to the type.

15

| .method <methodHead> { <methodBodyItem>* }

Declares a method of the type.

14

| .override <typeSpec> :: <methodName> with <callConv> <type> <typeSpec> :: <methodName> ( <parameters> )

Specifies that the first method is overridden by the definition of the second method.

9.3.2

| .pack <int32>

Used for explicit layout of fields.

9.7

| .property <propHead> { <propMember>* }

Declares a property of the type.

16

| .size <int32>

Used for explicit layout of fields.

9.7

| <externSourceDecl>

.line

5.7

| <securityDecl>

.permission or .capability

19

9.3 Introducing and Overriding Virtual Methods

A 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 Method

A 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.

ANNOTATION

The newslot and .override directives were introduced to improve versioning behavior. The newslot directive says that the marked method is a new contract, even if there is another method of the same name above it in the hierarchy.

Because CLI programmers have access to libraries and code from many external sources, name conflicts are possible, especially when implementing multiple interfaces. The .override directive makes it possible to avoid name conflicts. It is most often used when a class implements two interfaces, each of which has a method of the same name. Because a class cannot have two methods of the same name, the .override directive allows a programmer to specify that his implementation, with his name, is to override the specified interface method. The .override directive is used by C# to implement what it calls private implementations of an interface.

To make the VES easier to build, the .override directive requires that the implementation be provided in the same class as the .override directive. It is not possible to refer to an implementation that exists elsewhere.

For more information on newslot, hiding, and overriding, see Partition I, section 8.10.4.


9.3.2 The .override Directive

The .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).

<classMember> ::=

Section in Partition II

 

.override <typeSpec> :: <methodName> with <callConv> <type> <typeSpec> :: <methodName> ( <parameters> )

9.2

| ...

 

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 graphics/ccc.gif implementation is provided for a method declared in an interface (see Partition II, graphics/ccc.gif 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 graphics/ccc.gif be used to implement I::m on objects of class C.
9.3.3 Accessibility and Overriding

If 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.

Table 3-1. Legal Widening of Access to a Virtual Method

Subclass

Base Type Accessibility

 

private

family

assembly

famandassem

famorassem

public

private

Yes

No

No

No

No

No

family

Yes

Yes

No

No

If not in same assembly

No

assembly

Yes

No

Same assembly

No

No

No

famandassem

Yes

No

No

Same assembly

No

No

famorassem

Yes

Yes

Same assembly

Yes

Same assembly

No

public

Yes

Yes

Yes

Yes

Yes

Yes

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 Requirements

A type (concrete or abstract) may provide

  • Implementations for instance, static, and virtual methods that it introduces

  • Implementations for methods declared in interfaces that it has specified it will implement, or that its base type has specified it will implement

  • Alternative implementations for virtual methods inherited from its parent

  • Implementations for virtual methods inherited from an abstract base type that did not provide an implementation

A concrete (i.e., non-abstract) type shall provide, either directly or by inheritance, an implementation for

  • All methods declared by the type itself

  • All virtual methods of interfaces implemented by the type

  • All virtual methods that the type inherits from its base type

9.5 Special Members

There 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 Constructors

Instance 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 graphics/ccc.gif 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 } }

ANNOTATION

During the design of the CLI, there was an alternate design proposed with no notion of constructors. The Eiffel language (and design environment) does this, for example. In this alternate design, the programmer must assume that all objects can exist in an uninitialized state, where all their fields are known to be null. In this model, programmers must actively test to see whether an object has been initialized before using it.

In contrast, the adopted model requires that one of an object's constructors has been called by the time the constructor is finished executing, so the constructor can put the object into a non-null self-consistent state. This means that programmers do not have to use defensive test techniques.


9.5.2 Instance Finalizers

The 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 Initializers

Types 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 Guarantees

The CLI shall provide the following guarantees regarding type initialization (but see also Partition II, sections 9.5.3.2 and 9.5.3.3):

  1. When type initializers are executed is specified in Partition I, section 8.9.5.

  2. A type initializer shall run exactly once for any given type, unless explicitly called by user code.

  3. No method other than those called directly or indirectly from the type initializer will be able to access members of a type before its initializer completes execution.

9.5.3.2 Relaxed Guarantees

A 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 Deadlocks

In 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:

  1. Static variables of a type are in a known state prior to any access whatsoever.

  2. Type initialization alone shall not create a deadlock unless some code called from a type initializer (directly or indirectly) explicitly invokes blocking operations.

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 Types

Nested 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 graphics/ccc.gif field. The nested class may access both fields, while the enclosing class does not have graphics/ccc.gif 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 }

ANNOTATION

It is interesting to contrast the CLI's nested type with Java's inner classes, which share access to an instance of the class that encloses them. In the CLI, nested classes are little more than a naming convenience; the name of the nested class is scoped to the parent class. The only additional property of CLI nested types is that, like any member of the outer class, members of the nested class have access to the private fields of the enclosing type.


9.7 Controlling Instance Layout

The 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).

<fieldDecl> ::=

 

[[ <int32> ]] <fieldAttr>* <type> <id>

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.


ANNOTATION

The purpose of the .pack directive is to allow managed data to match the layout of pre-existing unmanaged data types.

Normally the .size directive is more useful for value types, but in COM interop it is also useful for classes.


 
 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 Methods

In 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:

  • If no member of the same kind (field or method), name, and signature exists, then add this member to the output class.

  • If there are duplicates and no more than one has an accessibility other than compilercontrolled, then add them all in the output class.

  • If there are duplicates and two or more have an accessibility other than compilercontrolled, an error has occurred.

ANNOTATION

The compilercontrolled accessibility means that the item so marked is accessible only from within a single compilation unit and under the control of the compiler. This is because it is defined as accessible only through use of a definition, not a reference.




The Common Language Infrastructure Annotated Standard (Microsoft. NET Development Series)
The Common Language Infrastructure Annotated Standard (Microsoft. NET Development Series)
ISBN: N/A
EAN: N/A
Year: 2002
Pages: 121

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net