8 Common Type System


Types describe values and specify a contract (see Partition 1, section 8.6) that all values of that type shall support. Because the CTS supports Object-Oriented Programming (OOP) as well as functional and procedural programming languages, it deals with two kinds of entities: Objects and Values. Values are simple bit patterns for things like integers and floats; each value has a type that describes both the storage that it occupies and the meanings of the bits in its representation, and also the operations that may be performed on that representation. Values are intended for representing the corresponding simple types in programming languages like C, and also for representing non-objects in languages like C++ and Java.

Objects have rather more to them than do values. Each object is self-typing; that is, its type is explicitly stored in its representation. It has an identity that distinguishes it from all other objects, and it has slots that store other entities (which may be either objects or values). While the contents of its slots may be changed, the identity of an object never changes.

There are several kinds of Objects and Values, as shown in Figure 2-1.

Figure 2-1. Type System

graphics/02fig01.gif

8.1 Relationship to Object-Oriented Programming

This section contains only informative text.


The term type is often used in the world of value-oriented programming to mean data representation. In the object-oriented world it usually refers to behavior rather than to representation. In the CTS, type is used to mean both of these things: two entities have the same type if and only if they have both compatible representations and behaviors. Thus, in the CTS, if one type is derived from a base type, then instances of the derived type may be substituted for instances of the base type because both the representation and the behavior are compatible.

In the CTS, unlike some OOP languages, two objects that have fundamentally different representations have different types. Some OOP languages use a different notion of type. They consider two objects to have the same type if they respond in the same way to the same set of messages. This notion is captured in the CTS by saying that the objects implement the same interface.

Similarly, some OOP languages (e.g., Smalltalk) consider message passing to be the fundamental model of computation. In the CTS, this corresponds to calling virtual methods (see Partition I, section 8.4.4), where the signature of the virtual method plays the role of the message.

The CTS itself does not directly capture the notion of "typeless programming." That is, there is no way to call a non-static method without knowing the type of the object. Nevertheless, typeless programming can be implemented based on the facilities provided by the reflection package (see the .NET Framework Standard Library Annotated Reference) if it is implemented.

ANNOTATION

Subclass is an OOP and computer science idea that approximates what a subtype is. Subtype is a mathematical idea that is very well defined but, as is true for many mathematical definitions, is too complicated for most programming languages. Instead, the idea of subclass is used.


Subclass means that there is an inheritance relationship that the subclass inherits from its parent class. If A is a subclass of B, then although it is true that A is a subtype of B, the inverse is not necessarily true it is possible for a subtype of something not to be its subclass.

End informative text


8.2 Values and Types

Types describe values. All places where values are stored, passed, or operated upon have a type e.g., all variables, parameters, evaluation stack locations, and method results. The type defines the allowable values and the allowable operations supported by the values of the type. All operators and functions have expected types for each of the values accessed or used.

A value can be of more than one type. A value that supports many interfaces is an example of a value that is of more than one type, as is a value that inherits from another.

8.2.1 Value Types and Reference Types

There are two kinds of types: Value Types and Reference Types.

  • Value Types Value Types describe values that are represented as sequences of bits.

  • Reference Types Reference Types describe values that are represented as the location of a sequence of bits. There are four kinds of Reference Types:

    • An object type is a reference type of a self-describing value (see Partition I, section 8.2.3). Some object types (e.g., abstract classes) are only a partial description of a value.

    • An interface type is always a partial description of a value, potentially supported by many object types.

    • A pointer type is a compile-time description of a value whose representation is a machine address of a location.

    • Built-in types.

ANNOTATION

Instances of value types are on the heap if they are boxed, a field of a class, or an element of an array. Instances of value types are on the stack if they are return values to methods, arguments of methods, local variables of methods, or fields of value types used in those ways. Instances of reference types are always allocated on the heap.

The definitions of built-in types must be supplied by the Virtual Execution System. The primary built-in types are value types, mainly the built-in numeric integer types, floating point types, and arrays. Also built into the VES is knowledge of some reference types: the base object type (System.Object), delegates, strings, and pointers.


8.2.2 Built-in Types

The data types in Table 2-1 are an integral part of the CTS and are supported directly by the Virtual Execution System (VES). They have special encoding in the persisted metadata:

ANNOTATION

Unlike all other types, built-in types do not require an explicit definition they need only be named in a signature. The VES must supply the definition when the type is named. Array and pointer definitions are also built into the VES.

With regard to the numeric types in Table 2-1, long discussions among language designers and CLI designers resulted in a decision to support only one type per length either signed or unsigned, not both. Notably, supporting unsigned int8, while not supporting signed int8, even though all other numeric types are signed, was controversial. Another decision was that char is distinct from the other numeric types, so it remains in the CLS.

Although there was general agreement that for most sizes, signed integers were generally more useful than unsigned integers, some parties, led by the C# designers, felt that unsigned bytes were more useful than signed bytes. Their reasoning was that bytes tend not to be used as signed values, and in fact, having to deal with signed bytes can cause certain programming problems. Bytes tend to be part of a larger structure, so values from 0 to 255 make more sense (the low byte of something is not given a negative value on its own). In arrays, the use of bytes is very limited, and if used at all, they would be unsigned bytes.

Some common errors result if programmers are forced to use signed bytes. If bytes were signed, programmers might have to mask the signed byte parameters to force them to be positive. Most programmers expect byte values to range from 0 to 255, not from 1 to 127, and omitting the mask might permit a negative value, thus producing a subtle bug. Another common problem could arise in building up a short from bytes; if the low byte is negative, it produces the wrong answer.


Table 2-1. Special Encoding

Name in CIL Assembler (see Partition II)

CLS Type?

Name in Class Library (see the .NET Framework Standard Library Annotated Reference)

Description

bool

Yes

System.Boolean

True/false value

char

Yes

System.Char

Unicode 16-bit char

object

Yes

System.Object

Object or boxed value type

string

Yes

System.String

Unicode string

float32

Yes

System.Single

IEC 60559:1989 32-bit float

float64

Yes

System.Double

IEC 60559:1989 64-bit float

int8

No

System.SByte

Signed 8-bit integer

int16

Yes

System.Int16

Signed 16-bit integer

int32

Yes

System.Int32

Signed 32-bit integer

int64

Yes

System.Int64

Signed 64-bit integer

native int

Yes

System.IntPtr

Signed integer, native size

native unsigned int

No

System.UIntPtr

Unsigned integer, native size

typedref

No

System.TypedReference

Pointer plus runtime type

unsigned int8

Yes

System.Byte

Unsigned 8-bit integer

unsigned int16

No

System.UInt16

Unsigned 16-bit integer

unsigned int32

No

System.UInt32

Unsigned 32-bit integer

unsigned int64

No

System.UInt64

Unsigned 64-bit integer

So although on the surface it may seem inconsistent to choose all signed integers except the unsigned byte for the CLS, it was, in the end, felt to be the more useful choice.

ANNOTATION

For a discussion of offset to string data, refer to the annotation in Partition IV, section 5.2.


8.2.3 Classes, Interfaces, and Objects

Every value has an exact type that fully describes the value. A type fully describes a value if it unambiguously defines the value's representation and the operations defined on the value.

For a Value Type, defining the representation entails describing the sequence of bits that make up the value's representation. For a Reference Type, defining the representation entails describing the location and the sequence of bits that make up the value's representation.

A method describes an operation that may be performed on values of an exact type. Defining the set of operations allowed on values of an exact type entails specifying named methods for each operation.

Some types are only a partial description e.g., interface types. Interface types describe a subset of the operations and none of the representation, and hence, cannot be an exact type of any value. Hence, while a value has only one exact type, it may also be a value of many other types as well. Furthermore, since the exact type fully describes the value, it also fully specifies all of the other types that a value of the exact type can have.

While it is true that every value has an exact type, it is not always possible to determine the exact type by inspecting the representation of the value. In particular, it is never possible to determine the exact type of a value of a Value Type. Consider two of the built-in Value Types, 32-bit signed and unsigned integers. While each type is a full specification of their respective values i.e., an exact type there is no way to derive that exact type from a value's particular 32-bit sequence.

For some values, called objects, it is always possible to determine the exact type from the value. Exact types of objects are also called object types. Objects are values of Reference Types, but not all Reference Types describe objects. Consider a value that is a pointer to a 32-bit integer, a kind of Reference Type. There is no way to discover the type of the value by examining the pointer bits, hence it is not an object. Now consider the built-in CTS Reference Type System.String (see the .NET Framework Standard Library Annotated Reference). The exact type of a value of this type is always determinable by examining the value, hence values of type System.String are objects and System.String is an object type.

ANNOTATION

Unlike classes, interfaces prescribe a set of behaviors (a contract), but cannot provide the implementations for those behaviors. Abstract classes can do that as well but need not provide the implementation, although they may.

People in the Object-Oriented (OO) community talk about the shape and the behavior of objects. The shape is determined by the fields the object will have in memory. The behavior is determined by the methods, events, properties, and interfaces that are available on the object.

Interfaces are simply a contract and may not have implementations associated with the interface type, other than implementations of static methods and definitions of static fields. Interfaces contribute to the behavior of an object that implements them, but they can never contribute to the shape because they are never allowed to define new fields. Their static fields determine only the shape of the interface itself, not the shape of the object. Static fields and static method implementations are not associated with instances of the interface. So interfaces contribute to behavior, not shape.

The exact type of an object instance is the most specific type of which it is an instance. For example, the exact type of the string "abc" is its parent type, System.String, rather than System.Object, because it is also, of course, an instance of an object. It has all of the behaviors of that type, and exactly its shape without anything additional.

Bordering on the realm of metaphysics, all objects have types. Some objects actually represent types, and their type is System.Type. Reflection has a method that returns the type of any object. If you want to explore the metaphysics of this notion, refer to The Art of the Metaobject Protocol, by Gregor Kiczales (MIT Press, 1991). In the CLI, you can determine the exact type of an object through use of the Reflection classes in the Base Class Library.


8.2.4 Boxing and Unboxing of Values

For every Value Type, the CTS defines a corresponding Reference Type called the boxed type. The reverse is not true: Reference Types do not in general have a corresponding Value Type. The representation of a value of a boxed type (a boxed value) is a location where a value of the Value Type may be stored. A boxed type is an object type, and a boxed value is an object.

All Value Types have an operation called box. Boxing a value of any Value Type produces its boxed value i.e., a value of the corresponding boxed type containing a bit copy of the original value. All boxed types have an operation called unbox. Unboxing results in a managed pointer to the bit representation of the value.

Notice that interfaces and inheritance are defined only on Reference Types. Thus, while a Value Type definition (see Partition I, section 8.9.7) can specify both interfaces that shall be implemented by the Value Type and the class (System.ValueType or System.Enum) from which it inherits, these apply only to boxed values.

CLS Rule 3: The CLS does not include boxed value types.

NOTE

In lieu of boxed types, use System.Object, System.ValueType or System.Enum, as appropriate. (See the .NET Framework Standard Library Annotated Reference.)

CLS (consumer): Need not import boxed value types.

CLS (extender): Need not provide syntax for defining or using boxed value types.

CLS (framework): Shall not use boxed value types in their publicly exposed aspects.


ANNOTATION

The CLI specifies that when a value type is defined, the VES creates a corresponding boxed type, which is a reference type. It is important to reiterate that the unbox operation does not literally remove the box. It does not take away the type header or pointer it simply provides a managed pointer to the value within the box. There is, of course, no corresponding creation of a value type when a reference type is defined.

When a programmer allocates a value type as a local variable, the VES allocates space for it and can zero that space if you set the zero init flag. To have verifiable code, that is a requirement. The "zero init flag" the .local init directive in assembler syntax, and the CorILMethod_InitLocals flag in the file format is described in Partition II, section 24.4.4.

A value type describes a layout in memory, plus the operations on it. The operations on it are also available through the corresponding boxed type. Some of the operations might involve changing the fields. When a value type is passed on a call, a copy is passed, so any operations on the call do not affect the original value. But since a boxed type is an object, it is always passed by reference, which means the caller is given a managed pointer to the original value. In that case, any changes to the value would change the original value. This is a potential problem.

Another issue is that it is not currently possible to create an array in which all of the entries must be the same boxed value type. For example, if you have Date/Time value types, you can create an array of those, but not an array of boxed Date/Time values.


8.2.5 Identity and Equality of Values

There are two binary operators defined on all pairs of values, identity and equality, that return a Boolean result. Both of these operators are mathematical equivalence operators; i.e., they are:

  • Reflexive a op a is true.

  • Symmetric a op b is true if and only if b op a is true.

  • Transitive if a op b is true and b op c is true, then a op c is true.

In addition, identity always implies equality, but not the reverse; i.e., the equality operator need not be the same as the identity operator as long as two identical values are also equal values.

To understand the difference between these operations, consider three variables whose type is System.String, where the arrow is intended to mean "is a reference to":

graphics/02inf01.gif

The values of the variables are identical if the locations of the sequences of characters are the same i.e., there is in fact only one string in memory. The values stored in the variables are equal if the sequences of characters are the same. Thus, the values of variables A and B are identical, the values of variables A and C as well as B and C are not identical, and the values of all three of A, B, and C are equal.

ANNOTATION

The statements at the beginning of this section:

There are two binary operators defined on all pairs of values, identity and equality, that return a Boolean result. Both of these operators are mathematical equivalence operators.

are mathematical truisms. From the point of view of computer science, these statements are perhaps a little optimistic. They certainly should be true, but will be true only if the programmers implementing the equality operator (i.e., overriding object.=) follow these guidelines.


8.2.5.1 Identity

The identity operator is defined by the CTS as follows.

  • If the values have different exact types, then they are not identical.

  • Otherwise, if their exact type is a Value Type, then they are identical if and only if the bit sequences of the values are the same, bit by bit.

  • Otherwise, if their exact type is a Reference Type, then they are identical if and only if the locations of the values are the same.

Identity is implemented on System.Object via the ReferenceEquals method.

8.2.5.2 Equality

For value types, the equality operator is part of the definition of the exact type. Definitions of equality should obey the following rules:

  • Equality should be an equivalence operator, as defined above.

  • Identity should imply equality, as stated earlier.

  • If either (or both) operand is a boxed value, equality should be computed by

    • First unboxing any boxed operand(s), and then

    • Applying the usual rules for equality on the resulting values.

Equality is implemented on System.Object via the Equals method.

NOTE

Although two floating point NaNs are defined by IEC 60559:1989 to always compare as unequal, the contract for System.Object.Equals requires that overrides must satisfy the requirements for an equivalence operator. Therefore, System.Double.Equals and System.Single.Equals return True when comparing two NaNs, while the equality operator returns False in that case, as required by the standard.


ANNOTATION

"NaN" stands for "not a number" and is a value returned on overflow conditions.


8.3 Locations

Values are stored in locations. A location can hold a single value at a time. All locations are typed. The type of the location embodies the requirements that shall be met by values that are stored in the location. Examples of locations are local variables and parameters.

More importantly, the type of the location specifies the restrictions on usage of any value that is loaded from the location. For example, a location can hold values of potentially many exact types as long as all of the values are assignment compatible with the type of the location (see below). All values loaded from a location are treated as if they are of the type of the location. Only operations valid for the type of the location may be invoked even if the exact type of the value stored in the location is capable of additional operations.

8.3.1 Assignment Compatible Locations

A value may be stored in a location only if one of the types of the value is assignment compatible with the type of the location. A type is always assignment compatible with itself. Assignment compatibility can often be determined at compile time, in which case there is no need for testing at runtime. Assignment compatibility is described in detail in Partition I, section 8.7.

8.3.2 Coercion

Sometimes it is desirable to take a value of a type that is not assignment compatible with a location and convert the value to a type that is assignment compatible. This is accomplished through coercion of the value. Coercion takes a value of a particular type and a desired type and attempts to create a value of the desired type that has equivalent meaning to the original value. Coercion can result in representation changes as well as type changes, hence coercion does not necessarily preserve the identity of two objects.

There are two kinds of coercion: widening, which never loses information, and narrowing, in which information may be lost. An example of a widening coercion would be coercing a value that is a 32-bit signed integer to a value that is a 64-bit signed integer. An example of a narrowing coercion is the reverse: coercing a 64-bit signed integer to a 32-bit signed integer. Programming languages often implement widening coercions as implicit conversions, whereas narrowing coercions usually require an explicit conversion.

Some widening coercion is built directly into the VES operations on the built-in types (see Partition I, section 12.1). All other coercion shall be explicitly requested. For the built-in types, the CTS provides operations to perform widening coercions with no runtime checks and narrowing coercions with runtime checks.

8.3.3 Casting

Since a value can be of more than one type, a use of the value needs to clearly identify which of its types is being used. Since values are read from locations that are typed, the type of the value which is used is the type of the location from which the value was read. If a different type is to be used, the value is cast to one of its other types. Casting is usually a compile-time operation, but if the compiler cannot statically know that the value is of the target type, a runtime cast check is done. Unlike coercion, a cast never changes the actual type of an object, nor does it change the representation. Casting preserves the identity of objects.

For example, a runtime check may be needed when casting a value read from a location that is typed as holding values of a particular interface. Since an interface is an incomplete description of the value, casting that value to be of a different interface type will usually result in a runtime cast check.

8.4 Type Members

As stated above, the type defines the allowable values and the allowable operations supported by the values of the type. If the allowable values of the type have a substructure, that substructure is described via fields or array elements of the type. If there are operations that are part of the type, those operations are described via methods on the type. Fields, array elements, and methods are called members of the type. Properties and events are also members of the type.

8.4.1 Fields, Array Elements, and Values

The representation of a value (except for those of built-in types) can be subdivided into sub-values. These sub-values are either named, in which case they are called fields, or they are accessed by an indexing expression, in which case they are called array elements. Types that describe values composed of array elements are array types. Types that describe values composed of fields are compound types. A value cannot contain both fields and array elements, although a field of a compound type may be an array type and an array element may be a compound type.

Array elements and fields are typed, and these types never change. All of the array elements shall have the same type. Each field of a compound type may have a different type.

8.4.2 Methods

A type may associate operations with the type or with each instance of the type. Such operations are called methods. A method is named and has a signature (see Partition I, section 8.6.1) that specifies the allowable types for all of its arguments and for its return value, if any.

A method that is associated only with the type itself (as opposed to a particular instance of the type) is called a static method (see Partition I, section 8.4.3).

A method that is associated with an instance of the type is either an instance method or a virtual method (see Partition I, section 8.4.4). When they are invoked, instance and virtual methods are passed the instance on which this invocation is to operate (known as this or a this pointer).

The fundamental difference between an instance method and a virtual method is in how the implementation is located. An instance method is invoked by specifying a class and the instance method within that class. The object passed as this may be null (a special value indicating that no instance is being specified) or an instance of any type that inherits (see Partition I, section 8.9.8) from the class that defines the method. A virtual method may also be called in this manner. This occurs, for example, when an implementation of a virtual method wishes to call the implementation supplied by its parent class. The CTS allows this to be null inside the body of a virtual method.

RATIONALE

Allowing a virtual method to be called with a non-virtual call eliminates the need for a "call super" instruction and allows version changes between virtual and non-virtual methods. It requires CIL generators to insert explicit tests for a null pointer if they don't want the null this pointer to propagate to called methods.


A virtual or instance method may also be called by a different mechanism, a virtual call. Any type that inherits from a type that defines a virtual method may provide its own implementation of that method (this is known as overriding, see Partition I, section 8.10.4). It is the exact type of the object (determined at runtime) that is used to decide which of the implementations to invoke.

ANNOTATION

There are three kinds of methods: static methods, virtual methods, and instance methods. Static methods are the same as functions or procedures in Pascal or C these are traditional functions with arguments, and reside in a class without being part of an object. Where you would put them in a class hierarchy depends on where users might be most likely to look for them, and on the other methods to which you want the static method to have access. Putting a static method in a class gives it access to the private data and methods of that class, and to family methods and data of all its parent classes.

Both virtual and instance methods are tied to an object. In addition to their other arguments, they get, implicitly from the VES, a this pointer (seen by the system as argument 0, although not specified by the programmer), which is an instance of the class in which they reside. Whereas static methods are just in a class, virtual methods are actually a part of the object itself. Virtual methods provide an implementation available to their children, but the children can also override it with their own implementation.

Instance methods include a definition of the method that cannot be changed, although it can be called by a child. Their location in a class is essentially for convenience, although they also have a this pointer. Any subclass can call them, but the method definition is provided with the instance methods themselves, and the child cannot change it in any way. Instance methods are a way of packaging useful code that child classes might want to use.

The CLI allows the programmer to call virtual or instance methods in one of two ways, which determine the source of the method implementation that will be used. You can make either a virtual call or an instance call on a virtual or instance method. The similarity of terms can be confusing, but it is important to understand the distinction. An instance call is made using the CIL call instruction (see Partition III, section 3.19). A virtual call is made with the CIL callvirt instruction (see Partition III, section 4.2).

For a virtual method, this determines the source of the implementation of the method that will be used. Because these methods get the same arguments regardless of how they are marked, the VES does not require that all virtual methods be called with a virtual call.

A virtual call takes the implementation that belongs to the exact object type associated with the virtual method at runtime. Instance calls, on the other hand, are not associated with an object but with a class, and an instance call uses the implementation of the method on the class indicated statically in the method signature in the metadata. Instance calls let the compiler determine the implementation, while virtual calls determine the implementation at runtime, based on the location of the this pointer.

For example, suppose there is an instance call to the virtual method Bob on class A (using the call instruction). In this case, Bob will always get class A's implementation, no matter where the this pointer is. A virtual call (using the callvirt instruction), on the other hand, will take the implementation from wherever the this pointer is if it is in subclass B, Bob gets subclass B's implementation.

For more information, see Partition II, section 14.2.

An important point to remember is that each specific call to a virtual method can be labeled as either a virtual or an instance call. In a virtual call, because the object determines the implementation, the this pointer cannot be null a null this pointer would require the VES to return an exception. Because instance calls call the virtual method's implementation in the class, the this pointer can be null. Some programming languages insert null checks at the beginning of virtual methods, to guarantee that the this pointer is not null, no matter how the virtual method is called.

A virtual call on an instance method (using callvirt) is generally used to ensure that the this pointer is not null, and then to make an instance call on that method.


8.4.3 Static Fields and Static Methods

Types may declare locations that are associated with the type rather than any particular value of the type. Such locations are static fields of the type. As such, static fields declare a location that is shared by all values of the type. Just like non-static (instance) fields, a static field is typed and that type never changes. Static fields are always restricted to a single application domain basis (see Partition I, section 12.5), but they may also be allocated on a per-thread basis.

Similarly, types may also declare methods that are associated with the type rather than with values of the type. Such methods are static methods of the type. Since an invocation of a static method does not have an associated value on which the static method operates, there is no this pointer available within a static method.

8.4.4 Virtual Methods

An object type may declare any of its methods as virtual. Unlike other methods, each exact type that implements the type may provide its own implementation of a virtual method. A virtual method may be invoked through the ordinary method call mechanism that uses the static type, method name, and types of parameters to choose an implementation, in which case the this pointer may be null. In addition, however, a virtual method may be invoked by a special mechanism (a virtual call) that chooses the implementation based on the dynamically detected type of the instance used to make the virtual call rather than the type statically known at compile time. Virtual methods may be marked final (see Partition I, section 8.10.2).

8.5 Naming

Names are given to entities of the type system so that they can be referred to by other parts of the type system or by the implementations of the types. Types, fields, methods, properties, and events have names. With respect to the type system, values, locals, and parameters do not have names. An entity of the type system is given a single name; e.g., there is only one name for a type.

8.5.1 Valid Names

All comparisons [of CLI names] are done on a byte-by-byte (i.e., case-sensitive, locale-independent, also known as code-point comparison) basis. Where names are used to access built-in VES-supplied functionality (for example, the class initialization method) there is always an accompanying indication on the definition so as not to build in any set of reserved names.

CLS Rule 4: Assemblies shall follow Annex 7 of Technical Report 15 of the Unicode Standard 3.0 (ISBN 0-201-61633-5) governing the set of characters permitted to start and be included in identifiers, available on-line at http://www.unicode.org/unicode/reports/tr15/tr15-18.html. Identifiers shall be in the canonical format defined by Unicode Normalization Form C. For CLS purposes, two identifiers are the same if their lowercase mappings (as specified by the Unicode locale-insensitive, 1-1 lowercase mappings) are the same. That is, for two identifiers to be considered different under the CLS they shall differ in more than simply their case. However, in order to override an inherited definition the CLI requires the precise encoding of the original declaration be used.

NOTE

CLS (consumer): Need not consume types that violate CLS rule 4, but shall have a mechanism to allow access to named items that use one of its own keywords as the name.

CLS (extender): Need not create types that violate CLS rule 4. Shall provide a mechanism for defining new names that obey these rules but are the same as a keyword in the language.

CLS (framework): Shall not export types that violate CLS rule 4. Should avoid the use of names that are commonly used as keywords in programming languages (see Partition V, Annex D).


ANNOTATION

One of the challenges in trying to make different languages work together is resolving the differences in valid names. The areas of difference are:

  • Case sensitivity

  • Variables from another language that may be keywords in your language

  • The characters that may be parts of identifiers

To resolve these areas of difference, the CLS rules require that compilers remember the case of names, and not permit externally visible names to have case-only differences. In addition, every language must have a means of identifying variables that are keywords in that language. The CLS also had to standardize on what characters could be part of identifiers, and Annex 7 of Technical Report 15 of the Unicode Standard was chosen.

Another issue is screen representation versus file encoding. The standard does not address the source code file format used as input to a compiler, but rather only the format of the runnable binary files produced by the compiler. This covers all cases except the class of characters that have two different 16-bit representations. These include characters, which include accents as part of the character. Unicode Normalization Form C, which specifies which representation to use, was chosen. For more information, see Partition I, section 10.2.


8.5.2 Assemblies and Scoping

Generally, names are not unique. Names are collected into groupings called scopes. Within a scope, a name may refer to multiple entities as long as they are of different kinds (methods, fields, nested types, properties, and events) or have different signatures.

CLS Rule 5: All names introduced in a CLS-compliant scope shall be distinct independent of kind, except where the names are identical and resolved via overloading. That is, while the CTS allows a single type to use the same name for a method and a field, the CLS does not.

CLS Rule 6: Fields and nested types shall be distinct by identifier comparison alone, even though the CTS allows distinct signatures to be distinguished. Methods, properties, and events that have the same name (by identifier comparison) shall differ by more than just the return type, except as specified in CLS Rule 39.

NOTE

CLS (consumer): Need not consume types that violate these rules after ignoring any members that are marked as not CLS-compliant.

CLS (extender): Need not provide syntax for defining types that violate these rules.

CLS (framework): Shall not mark types as CLS-compliant if they violate these rules unless they mark sufficient offending items within the type as not CLS-compliant so that the remaining members do not conflict with one another.


A named entity has its name in exactly one scope. Hence, to identify a named entity, both a scope and a name need to be supplied. The scope is said to qualify the name. Types provide a scope for the names in the type; hence types qualify the names in the type. For example, consider a compound type Point that has a field named x. The name "field x" by itself does not uniquely identify the named field, but the qualified name "field x in type Point" does.

ANNOTATION

The full qualification of a name is more complex and includes the unique identification of the assembly in which it resides.


Since types are named, the names of types are also grouped into scopes. To fully identify a type, the type name shall be qualified by the scope that includes the type name. Type names are scoped by the assembly that contains the implementation of the type. An assembly is a configured set of loadable code modules and other resources that together implement a unit of functionality. The type name is said to be in the assembly scope of the assembly that implements the type. Assemblies themselves have names that form the basis of the CTS naming hierarchy.

The type definition:

  • Defines a name for the type being defined i.e., the type name and specifies a scope in which that name will be found.

  • Defines a member scope in which the names of the different kinds of members (fields, methods, events, and properties) are bound. The tuple of (member name, member kind, and member signature) is unique within a member scope of a type.

  • Implicitly assigns the type to the assembly scope of the assembly that contains the type definition.

8.5.2a Enumeration Types

ANNOTATION

This section was inadvertently not given its own heading in the International Standard. To avoid renumbering sections, this section is given the number 8.5.2a in this book.


The CTS supports an enum (also known as an enumeration type), an alternate name for an existing type. For purposes of matching signatures an enum shall not be the same as the underlying type. Instances of an enum, however, shall be assignment compatible with the underlying type and vice versa. That is: no cast (see Partition I, section 8.3.3) or coercion (see Partition I, section 8.3.2) is required to convert from the enum to the underlying type, nor are they required from the underlying type to the enum. An enum is considerably more restricted than a true type:

  • It shall have exactly one instance field, and the type of that field defines the underlying type of the enumeration.

  • It shall not have any methods of its own.

  • It shall derive from System.Enum (see the .NET Framework Standard Library Annotated Reference).

  • It shall not implement any interfaces of its own.

  • It shall not have any properties or events of its own.

  • It shall not have any static fields unless they are literal (see Partition I, section 8.6.1).

The underlying type shall be a built-in integer type. Enums shall derive from System.Enum, hence they are value types. Like all value types, they shall be sealed (see Partition I, section 8.9.8.2).

CLS Rule 7: The underlying type of an enum shall be a built-in CLS integer type.

CLS Rule 8: There are two distinct kinds of enums, indicated by the presence or absence of the System.FlagsAttribute (see the .NET Framework Standard Library Annotated Reference) custom attribute. One represents named integer values; the other, named bit flags that can be combined to generate an unnamed value. The value of an enum is not limited to the specified values.

CLS Rule 9: Literal static fields (see Partition I, section 8.6.1) of an enum shall have the type of the enum itself.

NOTE

CLS (consumer): Shall accept definition of enums that follow these rules, but need not distinguish flags from named values.

CLS (extender): Same as consumer. Extender languages are encouraged to allow the authoring of enums, but need not do so.

CLS (framework): Shall not expose enums that violate these rules, and shall not assume that enums have only the specified values (even for enums that are named values).


8.5.3 Visibility, Accessibility, and Security

To refer to a named entity in a scope, both the scope and the name in the scope shall be visible (see Partition I, section 8.5.3.1). Visibility is determined by the relationship between the entity that contains the reference (the referent) and the entity that contains the name being referenced. Consider the following pseudo-code:

 
 class A { int32 IntInsideA; } class B inherits from A { void method X(int32, int32)   { IntInsideA := 15;   } } 

If we consider the reference to the field IntInsideA in class A:

  • We call class B the referent because it has a method that refers to that field,

  • We call IntInsideA in class A the referenced entity.

There are two fundamental questions that need to be answered in order to decide whether the referent is allowed to access the referenced entity. The first is whether the name of the referenced entity is visible to the referent. If it is visible, then there is a separate question of whether the referent is accessible (see Partition I, section 8.5.3.2).

Access to a member of a type is permitted only if all three of the following conditions are met:

  • The type is visible.

  • The member is accessible.

  • All relevant security demands (see Partition I, section 8.5.3.3) have been granted.

8.5.3.1 Visibility of Types

Only type names, not member names, have controlled visibility. Type names fall into one of the following three categories

  • Exported from the assembly in which they are defined. While a type may be marked to allow it to be exported from the assembly, it is the configuration of the assembly that decides whether the type name is made available.

  • Not exported outside the assembly in which they are defined.

  • Nested within another type. In this case, the type itself has the visibility of the type inside of which it is nested (its enclosing type). See Partition I, section 8.5.3.4.

8.5.3.2 Accessibility of Members

A type scopes all of its members, and it also specifies the accessibility rules for its members. Except where noted, accessibility is decided based only on the statically visible type of the member being referenced and the type and assembly that is making the reference. The CTS supports seven different rules for accessibility:

  • Compiler-Controlled accessible only through use of a definition, not a reference, hence only accessible from within a single compilation unit and under the control of the compiler.

  • Private accessible only to referents in the implementation of the exact type that defines the member.

  • Family accessible to referents that support the same type i.e., an exact type and all of the types that inherit from it For verifiable code (see Partition I, section 8.8), there is an additional requirement that may require a runtime check: the reference shall be made through an item whose exact type supports the exact type of the referent. That is, the item whose member is being accessed shall inherit from the type performing the access.

  • Assembly accessible only to referents in the same assembly that contains the implementation of the type.

  • Family-and-Assembly accessible only to referents that qualify for both Family and Assembly access.

  • Family-or-Assembly accessible only to referents that qualify for either Family or Assembly access.

  • Public accessible to all referents.

ANNOTATION

Not all languages support all of the possible accessibility rules. Languages support what they believe their users will need. For example, originally C# had only four accessibility levels: public, internal (assembly), protected (family), and private. The designers believed that was enough because a protected member could call an internal member. However, they received feedback from the framework developers that some members should be accessible only from within the assembly or by descendants outside, which is the family-or-assembly level. So C# implemented it, calling it protected internal. But the designers stood firm on not implementing family-and-assembly because they did not see it as useful. They still believe that assembly is usually enough it may have a few more permissions than necessary, but they feel that programmers usually just want to control access within the assembly.


In general, a member of a type can have any one of these accessibility rules assigned to it. There are two exceptions, however:

  1. Members defined by an interface shall be public.

  2. When a type defines a virtual method that overrides an inherited definition, the accessibility shall either be identical in the two definitions or the overriding definition shall permit more access than the original definition. For example, it is possible to override an assembly virtual method with a new implementation that is public virtual, but not with one that is family virtual. In the case of overriding a definition derived from another assembly, it is not considered restricting access if the base definition has family-or-assembly access and the override has only family access.

RATIONALE

Languages including C++ allow this "widening" of access. Restricting access would provide an incorrect illusion of security since simply casting an object to the base class (which occurs implicitly on method call) would allow the method to be called despite the restricted accessibility. To prevent overriding a virtual method, use final (see Partition I, section 8.10.2) rather than relying on limited accessibility.


CLS Rule 10: Accessibility shall not be changed when overriding inherited methods, except when overriding a method inherited from a different assembly with accessibility family-or-assembly. In this case the override shall have accessibility family.

NOTE

CLS (consumer): Need not accept types that widen access to inherited virtual methods.

CLS (extender): Need not provide syntax to widen access to inherited virtual methods.

CLS (frameworks): Shall not rely on the ability to widen access to a virtual method, either in the exposed portion of the framework or by users of the framework.


8.5.3.3 Security Permissions

Access to members is also controlled by security demands that may be attached to an assembly, type, method, property, or event. Security demands are not part of a type contract (see Partition I, section 8.6), and hence are not inherited. There are two kinds of demands:

  • An inheritance demand. When attached to a type, it requires that any type that wishes to inherit from this type shall have the specified security permission. When attached to a non-final virtual method, it requires that any type that wishes to override this method shall have the specified permission. It shall not be attached to any other member.

  • A reference demand. Any attempt to resolve a reference to the marked item shall have specified security permission.

Only one demand of each kind may be attached to any item. Attaching a security demand to an assembly implies that it is attached to all types in the assembly unless another demand of the same kind is attached to the type. Similarly, a demand attached to a type implies the same demand for all members of the type unless another demand of the same kind is attached to the member. For additional information, see Declarative Security in Partition II, section 19, and the classes in the System.Security namespace in the .NET Framework Standard Library Annotated Reference.

8.5.3.4 Nested Types

A type (called a nested type) can be a member of an enclosing type. A nested type has the same visibility as the enclosing type and has an accessibility as would any other member of the enclosing type. This accessibility determines which other types may make references to the nested type. That is, for a class to define a field or array element of a nested type, have a method that takes a nested type as a parameter or returns one as value, etc., the nested type shall be both visible and accessible to the referencing type. A nested type is part of the enclosing type, so its methods have access to all members of its enclosing type, as well as family access to members of the type from which it inherits (see Partition I, section 8.9.8). The names of nested types are scoped by their enclosing type, not their assembly (only top-level types are scoped by their assembly). There is no requirement that the names of nested types be unique within an assembly.

8.6 Contracts

Contracts are named. They are the shared assumptions on a set of signatures (see Partition I, section 8.6.1) between all implementers and all users of the contract. The signatures are the part of the contract that can be checked and enforced.

ANNOTATION

The standard does not specify structural equivalence, other than for arrays. A type named point (real x, real y) and a type rect (real x, real y) are not the same, despite having the same signatures. Some languages, such as C++, in its use of type templates, do use structural equivalence.


Contracts are not types; rather they specify requirements on the implementation of types. Types state which contracts they abide by i.e., which contracts all implementations of the type shall support. An implementation of a type can be verified to check that the enforceable parts of a contract, the named signatures, have been implemented. The kinds of contracts are:

  • Class contract A class contract is specified with a class definition. Hence, a class definition defines both the class contract and the class type. The name of the class contract and the name of the class type are the same. A class contract specifies the representation of the values of the class type. Additionally, a class contract specifies the other contracts that the class type supports e.g., which interfaces, methods, properties, and events shall be implemented. A class contract, and hence the class type, can be supported by other class types as well. A class type that supports the class contract of another class type is said to inherit from that class type.

  • Interface contract An interface contract is specified with an interface definition. Hence, an interface definition defines both the interface contract and the interface type. The name of the interface contract and the name of the interface type are the same. Many types can support an interface contract. Like a class contract, interface contracts specify which other contracts the interface supports e.g., which interfaces, methods, properties, and events shall be implemented.

    NOTE

    An interface type can never fully describe the representation of a value. Therefore an interface type can never support a class contract, and hence can never be a class type or an exact type.


  • Method contract A method contract is specified with a method definition. A method contract is a named operation that specifies the contract between the implementation(s) of the method and the callers of the method. A method contract is always part of a type contract (class, value type, or interface), and describes how a particular named operation is implemented. The method contract specifies the contracts that each parameter to the method shall support and the contracts that the return value shall support, if there is a return value.

  • Property contract A property contract is specified with a property definition. There is an extensible set of operations for handling a named value, which includes a standard pair for reading the value and changing the value [typically get and set]. A property contract specifies method contracts for the subset of these operations that shall be implemented by any type that supports the property contract. A type can support many property contracts, but any given property contract can be supported by exactly one type. Hence, property definitions are a part of the type definition of the type that supports the property.

  • Event contract An event contract is specified with an event definition. There is an extensible set of operations for managing a named event, which includes three standard methods (register interest in an event, revoke interest in an event, fire the event). An event contract specifies method contracts for all of the operations that shall be implemented by any type that supports the event contract. A type can support many event contracts, but any given event contract can be supported by exactly one type. Hence, event definitions are a part of the type definition of the type that supports the event.

8.6.1 Signatures

Signatures are the part of a contract that can be checked and automatically enforced. Signatures are formed by adding constraints to types and other signatures. A constraint is a limitation on the use of or allowed operations on a value or location. Example constraints would be whether a location may be overwritten with a different value or whether a value may ever be changed.

All locations have signatures, as do all values. Assignment compatibility requires that the signature of the value, including constraints, is compatible with the signature of the location, including constraints. There are four fundamental kinds of signatures: type signatures, location signatures, parameter signatures, and method signatures.

CLS Rule 11: All types appearing in a signature shall be CLS-compliant.

CLS Rule 12: The visibility and accessibility of types and members shall be such that types in the signature of any member shall be visible and accessible whenever the member itself is visible and accessible. For example, a public method that is visible outside its assembly shall not have an argument whose type is visible only within the assembly.

NOTE

CLS (consumer): Need not accept types whose members violate these rules.

CLS (extender): Need not provide syntax to violate these rules.

CLS (framework): Shall not violate this rule in its exposed types and their members.


The following sections describe the various kinds of signatures. These descriptions are cumulative: the simplest signature is a type signature; a location signature is a type signature plus (optionally) some additional attributes; and so forth.

ANNOTATION

Signatures store both constraints and modifiers. Both have similar functions, but constraints are defined by the standard, and modifiers are defined by implementers. Both are stored in the signatures in the metadata, in compact form. They are distinct from attributes, which are much more complex. Modifiers are often referred to as custom modifiers, and they can be designated either optional or required (modopt or modreq, respectively, as described in Partition II, section 7.1.1).


8.6.1.1 Type Signatures

Type signatures define the constraints on a value and its usage. A type, by itself, is a valid type signature. The type signature of a value cannot be determined by examining the value or even by knowing the class type of the value. The type signature of a value is derived from the location signature (see below) of the location from which the value is loaded. Normally the type signature of a value is the type in the location signature from which the value is loaded.

ANNOTATION

Currently there are no constraints that can be placed on types, although at one point in the design, it was possible to designate a type as constant. At present, only location constraints, described in the next section, are possible. Type signatures are standardized to allow the possibility of introducing type constraints in the future.


RATIONALE

The distinction between a Type Signature and a Location Signature (below) is not currently useful. It is made because certain constraints, such as "constant," are constraints on values, not locations. Future versions of this standard, or non-standard extensions, may introduce type constraints, thus making the distinction meaningful.


8.6.1.2 Location Signatures

All locations are typed. This means that all locations have a location signature, which defines constraints on the location, its usage, and on the usage of the values stored in the location. Any valid type signature is a valid location signature. Hence, a location signature contains a type and may additionally contain the constant constraint. The location signature may also contain location constraints that give further restrictions on the uses of the location. The location constraints are:

  • The init-only constraint promises (hence, requires) that once the location has been initialized, its contents never change. Namely, the contents are initialized before any access, and after initialization, no value may be stored in the location. The contents are always identical to the initialized value (see Partition I, section 8.2.3). This constraint, while logically applicable to any location, shall only be placed on fields (static or instance) of compound types.

  • The literal constraint promises that the value of the location is actually a fixed value of a built-in type. The value is specified as part of the constraint. Compilers are required to replace all references to the location with its value, and the VES therefore need not allocate space for the location. This constraint, while logically applicable to any location, shall only be placed on static fields of compound types. Fields that are so marked are not permitted to be referenced from CIL (they shall be inlined to their constant value at compile time), but are available using Reflection and tools that directly deal with the metadata.

CLS Rule 13: The value of a literal static is specified through the use of field initialization metadata (see Partition II). A CLS-compliant literal must have a value specified in field initialization metadata that is of exactly the same type as the literal (or of the underlying type, if that literal is an enum).

NOTE

CLS (consumer): Must be able to read field initialization metadata for static literal fields and inline the value specified when referenced. Consumers may assume that the type of the field initialization metadata is exactly the same as the type of the literal field; i.e., a consumer tool need not implement conversions of the values.

CLS (extender): Must avoid producing field initialization metadata for static literal fields in which the type of the field initialization metadata does not exactly match the type of the field.

CLS (framework): Should avoid the use of syntax specifying a value of a literal that requires conversion of the value. Note that compilers may do the conversion themselves before persisting the field initialization metadata resulting in a CLS-compliant framework, but frameworks are encouraged not to rely on such implicit conversions.


NOTE

It might seem reasonable to provide a volatile constraint on a location that would require that the value stored in the location not be cached between accesses. Instead, CIL includes a volatile. prefix to certain instructions to specify that the value neither be cached nor computed using an existing cache. Such a constraint may be encoded using a custom attribute (see Partition I, section 9.7), although this standard does not specify such an attribute.


ANNOTATION

Specifiying volatile as a prefix rather than as a constraint provides more flexibility. It allows greater code optimization than a constraint, which would apply to all variables or types so labeled. CLS rule 13 tells how to specify the value of literal statics. Although you can reflect on a literal static, it is not possible to go in with a tool, such as a debugger, and change its value.


ANNOTATION

Some programming languages, such as C and C++, support the notion of passing explicit pointers. Others, such as Pascal, support the notion of passing a variable by reference. Still other languages, such as Eiffel and Lisp, do not support the notion at all. Microsoft received significant opposition to the inclusion of the managed pointer in the CLI, and more importantly, to putting it in the CLS, and therefore in libraries.

The decision to include it was largely so that library routines such as atomic increment, atomic decrement, volatile reference, etc., could be provided in a language-independent manner. Without managed pointers or a similar mechanism, these could not be written in libraries, but would have had to be added directly to programming languages. I feel strongly that language-independent access to these kinds of facilities is essential in a modern programming world.

Jim Miller


8.6.1.3 Local Signatures

A local signature specifies the contract on a local variable allocated during the running of a method. A local signature contains a full location signature, plus it may specify one additional constraint:

The byref constraint states that the content of the corresponding location is a managed pointer. A managed pointer may point to a local variable, parameter, field of a compound type, or element of an array. However, when a call crosses a remoting boundary (see Partition I, section 12.5) a conforming implementation may use a copy-in/copy-out mechanism instead of a managed pointer. Thus programs shall not rely on the aliasing behavior of true pointers.

In addition, there is one special local signature. The typed reference local variable signature states that the local will contain both a managed pointer to a location and a runtime representation of the type that may be stored at that location. A typed reference signature is similar to a byref constraint, but while the byref specifies the type as part of the byref constraint (and hence as part of the type description), a typed reference provides the type information dynamically. A typed reference is a full signature in itself and cannot be combined with other constraints. In particular, it is not possible to specify a byref whose type is typed reference.

The typed reference signature is actually represented as a built-in value type, like the integer and floating point types. In the Base Class Library (see .NET Framework Standard Library Annotated Reference) the type is known as System.TypedReference, and in the assembly language used in Partition II it is designated by the keyword typedref. This type shall only be used for parameters and local variables. It shall not be boxed, nor shall it be used as the type of a field, element of an array, return value, etc.

ANNOTATION

The typed reference is a reference to a memory location in which only a specified type can be stored. Although it is an elegant idea based on a notion from Visual Basic 6, most languages do not currently support it (including the current version of Visual Basic), because it requires special syntax and there some restrictions on what can be done with it, as described above.

Typed references are not required in the Kernel Profile (see Partition IV, section 4), so it is possible that some VES implementations will not support them.


CLS Rule 14: Typed references are not CLS-compliant.

NOTE

CLS (consumer): There is no need to accept this type.

CLS (extender): There is no need to provide syntax to define this type or to extend interfaces or classes that use this type.

CLS (framework): This type shall not appear in exposed members.


8.6.1.4 Parameter Signatures

Parameter signatures define constraints on how an individual value is passed as part of a method invocation. Parameter signatures are declared by method definitions. Any valid local signature is a valid parameter signature.

8.6.1.5 Method Signatures

Method signatures are composed of

  • A calling convention,

  • A list of zero or more parameter signatures, one for each parameter of the method,

  • And a type signature for the result value if one is produced.

Method signatures are declared by method definitions. Only one constraint can be added to a method signature in addition to those of parameter signatures:

  • The varargs constraint may be included to indicate that all arguments past this point are optional. When it appears, the calling convention shall be one that supports variable[-length] argument lists.

Method signatures are used in two different ways. They are used as part of a method definition and as a description of a calling site when calling through a function pointer. In this latter case, the method signature indicates

  • The calling convention (which may include platform-specific calling conventions)

  • The type of all the argument values that are being passed,

  • If needed, a varargs marker indicating where the fixed parameter list ends and the variable parameter list begins

When used as part of a method definition, the varargs constraint is represented by the choice of calling convention.

ANNOTATION

Because of the special handling required for the varargs constraint, many languages choose not to support this option. In addition, the varargs library is not in the Kernel Profile, so it is not required of a base VES implementation.


CLS Rule 15: The varargs constraint is not part of the CLS, and the only calling convention supported by the CLS is the standard managed calling convention.

NOTE

CLS (consumer): There is no need to accept methods with variable[-length] argument lists or unmanaged calling conventions.

CLS (extender): There is no need to provide syntax to declare varargs methods or unmanaged calling conventions.

CLS (framework): Neither varargs methods nor methods with unmanaged calling conventions may be exposed externally.


8.7 Assignment Compatibility

The constraints in the type signature and the location signature affect assignment compatibility of a value to a location. Assignment compatibility of a value (described by a type signature) to a location (described by a location signature) is defined as follows:

One of the types supported by the exact type of the value is the same as the type in the location signature.

This allows, for example, an instance of a class that inherits from a base class (hence supports the base class's type contract) to be stored into a location whose type is that of the base class.

8.8 Type Safety and Verification

Since types specify contracts, it is important to know whether a given implementation lives up to these contracts. An implementation that lives up to the enforceable part of the contract (the named signatures) is said to be typesafe. An important part of the contract deals with restrictions on the visibility and accessibility of named items as well as the mapping of names to implementations and locations in memory.

ANNOTATION

As Figure 2-2 shows, not all CTS programs will be memory-safe, and not all memory-safe programs will be verifiable.

Figure 2-2. Type Safety and Programs

graphics/02fig02.gif

In trying to define a platform for many languages, a primary goal is to ensure code that is safe. Although the standard talks about type safety, in the area of verification it is, strictly speaking, memory safety that verifiers ensure.

Type safety is a well-defined mathematical concept that says that the only operations on the type are those that the type was designed to support. The verifier does not require that. For example, it is not typesafe to take a long and cast it to a 32-bit int. Because you have lost information, it is not strictly typesafe. But it does not damage memory, and most languages allow you to do it as do verifiers (such as Microsoft's PEVerify) constructed using the verification algorithm and rules described in Partition III. However, the verifier enforces type safety according to a much smaller type system than the entire range specified by the Common Type System (CTS), and it guarantees memory safety.

However, plenty of programs are safe but not verifiable. For example, C and C++ are both capable of doing many operations in memory that are not verifiable. They must therefore rely on standard testing techniques to be sure the code is safe. In contrast, Eiffel has its own constructs that are perfectly safe it has a strong development environment that ensures safety.


Typesafe implementations only store values described by a type signature in a location that is assignment compatible with the location signature of the location (see Partiton I, section 8.6.1). Typesafe implementations never apply an operation to a value that is not defined by the exact type of the value. Typesafe implementations only access locations that are both visible and accessible to them. In a typesafe implementation, the exact type of a value cannot change.

Verification is a mechanical process of examining an implementation and asserting that it is typesafe. Verification is said to succeed if the process proves that an implementation is typesafe. Verification is said to fail if that process does not prove the type safety of an implementation. Verification is necessarily conservative: it may report failure for a typesafe implementation, but it never reports success for an implementation that is not typesafe. For example, most verification processes report implementations that do pointer-based arithmetic as failing verification, even if the implementation is in fact typesafe.

There are many different processes that can be the basis of verification. The simplest possible process simply says that all implementations are not typesafe. While correct and efficient, this is clearly not particularly useful. By spending more resources (time and space), a process can correctly identify more typesafe implementations. It has been proven, however, that no mechanical process can in finite time and with no errors correctly identify all implementations as either typesafe or not typesafe. The choice of a particular verification process is thus a matter of engineering, based on the resources available to make the decision and the importance of detecting the type safety of different programming constructs.

8.9 Type Definers

Type definers construct a new type from existing types. Implicit types (e.g., built-in types, arrays, and pointers, including function pointers) are defined when they are used. The mention of an implicit type in a signature is in and of itself a complete definition of the type. Implicit types allow the VES to manufacture instances with a standard set of members, interfaces, etc. Implicit types need not have user-supplied names.

All other types shall be explicitly defined using an explicit type definition. The explicit type definers are:

  • Interface definitions used to define interface types

  • Class definitions used to define:

    • Object types

    • Value types and their associated boxed types

NOTE

While class definitions always define class types, not all class types require a class definition. Array types and pointer types, which are implicitly defined, are also class types. See Partition I, section 8.2.3.

Similarly, not all types defined by a class definition are object types. Array types, explicitly defined object types, and boxed types are object types. Pointer types, function pointer types, and value types are not object types. See Partition I, section 8.2.3.


8.9.1 Array Types

An array type shall be defined by specifying the element type of the array, the rank (number of dimensions) of the array, and the upper and lower bounds of each dimension of the array. Hence, no separate definition of the array type is needed. The bounds (as well as indices into the array) shall be signed integers. While the actual bounds for each dimension are known at runtime, the signature may specify the information that is known at compile time: no bounds, a lower bound, or both an upper and lower bound.

Array elements shall be laid out within the array object in row-major order; i.e., the elements associated with the rightmost array dimension shall be laid out contiguously from lowest to highest index. The actual storage allocated for each array element may include platform-specific padding.

Values of an array type are objects; hence an array type is a kind of object type (see Partition I, section 8.2.3). Array objects are defined by the CTS to be a repetition of locations where values of the array element type are stored. The number of repeated values is determined by the rank and bounds of the array.

Only type signatures, not location signatures, are allowed as array element types.

Exact array types are created automatically by the VES when they are required. Hence, the operations on an array type are defined by the CTS. These generally are: allocating the array based on size and lower bound information, indexing the array to read and write a value, computing the address of an element of the array (a managed pointer), and querying for the rank, bounds, and the total number of values stored in the array.

ANNOTATION

Arrays are seen by the system as objects and are one of the implicit types declaring the type, rank, and bounds alone causes the VES to define the array. Other implicit types are the built-in numeric types, pointers, the base object type, delegates, and strings. The compiler sees only the reference to the array, and the VES constructs the objects. This makes the methods associated with System.Array (the Get, Set, and Address methods) available as well.

In deciding how to define arrays for this standard, there were several difficult questions to answer during the design of the CLI: whether arrays should be covariant or invariant, the allowed dimensions for an array, and the allowed array bounds.

If covariant arrays are supported, anywhere you have an array of a given type you can have an array of any subclass of that type. Invariant arrays mean that an array of a subclass of a type is not a subclass of an array of the type. The following short program will illustrate the difference between covariant and invariant.

 
 class Vehicle { .... } class Car : Vehicle { ... } void M(Vehicle[] MyVehicles) { Vehicle V = new Vehicle();   Car C = new Car();   MyVehicles[0] = V;   MyVehicles[1] = C; } void Main() { Car[] MyCars = new Car[2];   M(MyCars); } 

main() makes an array of cars, and passes it into M. The code inside of M is legal code for both covariant and invariant arrays. The issue is whether main() is legal. With invariant arrays, the call to M in main() is not verifiable, and in fact, the compiler should return an error because an array of Car is not a subclass of an array of Vehicle.

The covariant approach is that anywhere you have an array of any subclass of that type, you can have an array of that type's subclass anywhere you have an array of Vehicles, you can have an array of Cars. The problem is that in order to do it, and still make M legal, at runtime the assignments of V and C into the array must be checked to see if they are legal. In a covariant system, the assignment of C turns out to be legal, but there is no way to know that prior to runtime. The assignment of V is not legal, because instead of an array of Vehicles, main() supplied an array of Cars, so Vehicles cannot be in that array.

In a system that requires invariant arrays, storing into an array would not require any type checking at runtime, but it would not have let us write this program. With covariant arrays, this program is legal, but the penalty is that anytime you store into an array, or take the address of an element of an array, a type check is required.

The decision to support covariant arrays was primarily to allow Java to run on the VES. The covariant design is not thought to be the best design in general, but it was chosen in the interest of broad reach.

The dimensionality of allowed arrays was also an issue. The decision was made to support multi-dimensional arrays, but not to support zero-dimensional arrays (allowed currently only in APL).

Array bounds were another question. The options were always 0 for lower, always 1 for lower, or user specifies both upper and lower bounds. Here the CTS and the CLS took different paths. The CTS supports all of these (lower bounds specified arbitrarily by the programmer).


CLS Rule 16: Arrays shall have elements with a CLS-compliant type, and all dimensions of the array shall have lower bounds of zero. Only the fact that an item is an array and the element type of the array shall be required to distinguish between overloads. When overloading is based on two or more array types, the element types shall be named types.

NOTE

So-called "jagged arrays" are CLS-compliant, but when overloading multiple array types they are one-dimensional, zero-based arrays of type System.Array.

CLS (consumer): There is no need to support arrays of non-CLS types, even when dealing with instances of System.Array. Overload resolution need not be aware of the full complexity of array types. Programmers should have access to the Get, Set, and Address methods on instances of System.Array if there is no language syntax for the full range of array types.

CLS (extender): There is no need to provide syntax to define non-CLS types of arrays or to extend interfaces or classes that use non-CLS array types. Shall provide access to the type System.Array, but may assume that all instances will have a CLS-compliant type. While the full array signature must be used to override an inherited method that has an array parameter, the full complexity of array types need not be made visible to programmers. Programmers should have access to the Get, Set, and Address methods on instances of System.Array if there is no language syntax for the full range of array types.

CLS (framework): Non-CLS array types shall not appear in exposed members. Where possible, use only one-dimensional, zero-based arrays (vectors) of simple named types, since these are supported in the widest range of programming languages. Overloading on array types should be avoided, and when used shall obey the restrictions.


ANNOTATION

The CLS has one rule (rule 16) for arrays, which implies three things:

  1. The type of the element must be CLS-compliant.

  2. The only arrays supported for interlanguage use must have lower bounds of zero.

  3. Overloading a method based on arrays is severely restricted.

Imagine a method named "Ralph." The following overloadings of Ralph are legal:

void Ralph()

void Ralph(int) differs by arity (number of arguments)

void Ralph(float) differs by simple type

void Ralph(int[])

void Ralph(float[]) arrays differ by type of element

The following are not legal:

void Ralph(int *[]) element type doesn't have a name

void Ralph(int[][]) element type doesn't have a name

void Ralph(int[,]) array has same element type

void Ralph(int[1,]) array has same element type

C and Java do not support multi-dimensional arrays, just arrays of arrays (called jagged arrays). For purposes of overloading, however, these look like one-dimensional arrays with a zero-based index, and the element type is called System.Array. Therefore, you cannot define a jagged array of integers and a jagged array of floating point numbers and overload on that distinction:

void Ralph(int [][]) C/Java jagged array of integers

void Ralph(float [][]) C/Java jagged array of floats

Neither of these has a simple name for the element type of the array the first is int[], the second is float[].


Array types form a hierarchy, with all array types inheriting from the type System.Array. This is an abstract class (see Partition I, section 8.9.6.2) that represents all arrays regardless of the type of their elements, their rank, or their upper and lower bounds. The VES creates one array type for each distinguishable array type. In general, array types are only distinguished by the type of their elements and their rank. The VES, however, treats single-dimensional, zero-based arrays (also known as vectors) specially. Vectors are also distinguished by the type of their elements, but a vector is distinct from a single-dimensional array of the same element type that has a non-zero lower bound. Zero-dimensional arrays are not supported.

ANNOTATION

The only language that supports zero-dimensional arrays is APL. Although there was discussion about supporting them in the CLI, it was felt, in the end, that the difficulties entailed in supporting it outweighed the benefits.


Consider the examples in Table 2-2, using the syntax of CIL as described in Partition II:

ANNOTATION

The CLI supports multi-dimensional arrays. The specification requires that the arrays be laid out in memory as dense arrays in a row-major form (see Partition I, section 8.9.1). Figure 2-3(a) shows a row-major layout for a two-dimensional array with three rows and two columns.

Row-major array layout was chosen because many existing applications assume that layout. During the discussions of the standard, it was noted that a future standard may add new array types whose layout is not specified and can be chose by the implementation to fit a given application best. That new array element would make pointer arithmetic on pointers to array elements meaningless and would allow access only through standard member functions like these that are declared in System.Array.

Table 2-2. Array Examples

Static Specification of Type

Actual Type Constructed

Allowed in CLS?

int32[]

vector of int32

Yes

int32[0..5]

vector of int32

Yes

int32[1..5]

array, rank 1, of int32

No

int32[,]

array, rank 2, of int32

Yes

int32[0..3, 0..5]

array, rank 2, of int32

Yes

int32[0.., 0..]

array, rank 2, of int32

Yes

int32[1.., 0..]

array, rank 2, of int32

No

Figure 2-3. Typical Array Layouts in Memory

graphics/02fig03.gif

In the future, the VES might choose a layout different from the default row-major format for a couple of reasons:

  • Other layouts may provide better performance by providing better spatial locality and exploiting processor caches more efficiently; e.g., a column-major layout may be beneficial in some circumstances; see Figure 2-3(b).

  • Sparse arrays (i.e., arrays in which the majority of elements are zero) may be represented in a way that uses less memory and makes operations on larger data sets possible.

The VES might choose a different array layout at the time the array is created, or it might even remap an existing array by changing its layout to react to newly available information about the application behavior.

A decision about an array layout can be made completely automatically by the use of compiler analysis or by instrumentation of the runtime behavior, or it may be decided with the programmer's help by allowing an annotation or an extra argument in the array constructor.

Michal Cierniak, Intel Corporation


8.9.2 Unmanaged Pointer Types

An unmanaged pointer type (also known simply as a "pointer type") is defined by specifying a location signature for the location the pointer references. Any signature of a pointer type includes this location signature. Hence, no separate definition of the pointer type is needed.

While pointer types are Reference Types, values of a pointer type are not objects (see Partition I, section 8.2.3), and hence it is not possible, given a value of a pointer type, to determine its exact type. The CTS provides two typesafe operations on pointer types: one to load the value from the location referenced by the pointer and the other to store an assignment compatible value into that location. The CTS also provides three operations on pointer types (byte-based address arithmetic): adding and subtracting integers from pointers, and subtracting one pointer from another. The results of the first two operations are pointers to the same type signature as the original pointer. See Partition III for details.

CLS Rule 17: Unmanaged pointer types are not CLS-compliant.

NOTE

CLS (consumer): There is no need to support unmanaged pointer types.

CLS (extender): There is no need to provide syntax to define or access unmanaged pointer types.

CLS (framework): Unmanaged pointer types shall not be externally exposed.


ANNOTATION

Unmanaged pointers are specified by the CTS because it is important to have tools that deal with both managed and unmanaged code.

Not described in this part of Partition I (other than a brief mention in the discussion of the byref constraint in section 8.6.1.3) is another important type: managed pointers. Managed pointers, strictly speaking, are not a data type, but a modifier to a data type. An object reference points to the start of an object or array, while a managed pointer points to the interior of an object to one of its fields. Because it is managed, you have access to the field to which it points, even if the object moves. Managed pointers are allowed only in local variables, parameters to methods, and the return value of a method.

A managed pointer can also point to a value type. When the value type is boxed, making it an object, it points to the value in the interior of the object on the heap. But a managed pointer can also point directly to a value type on the stack.

In C# the managed pointer surfaces as an out parameter, but it is not possible to have a variable of that type. You would not declare a variable named x of type managed pointer to integer. It is instead a parameter passing convention. You would have a parameter named x, which is an out parameter to an integer. Historically, this is the same thing that Pascal called a var parameter. In Visual Basic it was called a byref rather than a byval parameter.

When a managed pointer is treated as an out parameter, its definition as a modifier (constraint) makes sense. But for many other languages, and their exposure of managed pointers, it makes more sense to think of it as a type with restrictions. Managed pointers are allowed only in local variables, parameters to methods, and the return value of a method. Returning a managed pointer is not, however, verifiable.

If you are writing unmanaged code and want to deal with managed data, there are three options: (1) pass a managed pointer to a pinned object on the heap, (2) pass an unmanaged pointer to a value on the stack, or (3) pass an instance of the System.Runtime.InteropServices.GCHandle structure, where the handle was created as "pinned" to a value on the heap. It is strongly recommended that you avoid the first of these options.

For more information on managed pointers, see Partition II, section 13.4.2, and Partition III, section 1.1.4.2.


8.9.3 Delegates

Delegates are the object-oriented equivalent of function pointers. Unlike function pointers, delegates are object-oriented, typesafe, and secure. Delegates are created by defining a class that derives from the base type System.Delegate (see the .NET Framework Standard Library Annotated Reference). Each delegate type shall provide a method named Invoke with appropriate parameters, and each instance of a delegate forwards calls to its Invoke method to a compatible static or instance method on a particular object. The object and method to which it delegates are chosen when the delegate instance is created.

In addition to an instance constructor and an Invoke method, delegates may optionally have two additional methods: BeginInvoke and EndInvoke. These are used for asynchronous calls.

While, for the most part, delegates appear to be simply another kind of user-defined class, they are tightly controlled. The implementations of the methods are provided by the VES, not user code. The only additional members that may be defined on delegate types are static or instance methods.

ANNOTATION

Delegates are a good object-oriented model for what is a function pointer in the unmanaged world. Delegates are themselves objects with two private fields, one of which is an object, and the other of which is the method that can be called on that object. They can also contain no object and a static method.

Delegates can be single-cast or multicast. The simplest form of delegate single-cast corresponds to a simple function pointer in the non-OO world.

Multicast delegates call all of the single-cast delegates.

One of the advantages of delegates is that they can be called asynchronously, unlike function pointers. Calling a delegate asynchronously means that another thread makes the actual call, and the original caller can check back later for the result.

Delegates may also point to multiple functions. In this case, calling the delegate means that all of the functions get called, and there are methods that allow you to set or change the order in which they are called. Thus, a programmer can write code that will call functions in a given order, rearrange it, and catch all the exceptions.

But delegates are particularly nice for the event model (publish and subscribe). It is possible to provide a delegate that is not intended to call a function but allows other objects to add themselves to it, and to notify them that a particular event, such as a left-hand mouse click, has occurred. When the event occurs, the delegate publishes the event to all subscribers.

There is support for events in the metadata, but not directly in the VES. Whatever syntax compilers use for events, underneath they are just calling the delegate associated with it.

Although the VES builds in support for delegates by providing the class System.Delegate, delegates are slightly different from the other built-in types because you must declare the delegate class. However, because System.Delegate is an abstract class, you cannot make instances of it. Therefore, you need another class, which inherits from System.Delegate, that can have an instance made from it.

To declare a delegate, it is only necessary to declare a subclass of the wrapper that programming languages provide for System.Delegate, which then provides the values (such as arguments to the Invoke method and the return type value). The VES then creates the delegate, and builds everything else in. Programmers just see the Invoke, GetInvoke, and EndInvoke methods. When a delegate is declared, it can't have any fields, properties, or enumerations; it can have only these methods.

For more detailed information on declaring delegates, and on the delegate methods, see Partition II, section 13.6.


8.9.4 Interface Type Definition

An interface definition defines an interface type. An interface type is a named group of methods, locations, and other contracts that shall be implemented by any object type that supports the interface contract of the same name. An interface definition is always an incomplete description of a value, and as such can never define a class type or an exact type, nor can it be an object type.

Zero or more object types can support an interface type, and only object types can support an interface type. An interface type may require that objects that support it shall also support other (specified) interface types. An object type that supports the named interface contract shall provide a complete implementation of the methods, locations, and other contracts specified (but not implemented by) the interface type. Hence, a value of an object type is also a value of all of the interface types the object type supports. Support for an interface contract is declared, never inferred; i.e., the existence of implementations of the methods, locations, and other contracts required by the interface type does not imply support of the interface contract.

ANNOTATION

Interfaces specify a set of method contracts. To provide an interface, an object must implement its methods there is no implementation of the methods associated with the interface itself. An interface, in itself, may also include static field or static method definitions with implementations, but it may not include any non-static method implementations (although CLS-compliant interfaces may define neither static fields nor static methods). Therefore, while an interface contributes to the behavior of an object, it does not contribute to its shape (static fields and methods are not associated with instances of the interface within an object).

Interfaces often specify other interfaces within their contract. An object using an interface must implement all other interfaces specified within that interface as well. This is not, however, inheritance in the way that a class may inherit its implementations from a parent class, because no implementations are associated with any of the interfaces directly. Instead, when writing a class that uses an interface that specifies one or more other interfaces, that class must implement all of those interfaces for the contract to be fulfilled.


CLS Rule 18: CLS-compliant interfaces shall not require the definition of non-CLS-compliant methods in order to implement them.

NOTE

CLS (consumer): There is no need to deal with such interfaces.

CLS (extender): Need not provide a mechanism for defining such interfaces..

CLS (framework): Shall not expose any non-CLS-compliant methods on interfaces it defines for external use.


Interfaces types are necessarily incomplete, since they say nothing about the representation of the values of the interface type. For this reason, an interface type definition shall not provide field definitions for values of the interface type (i.e., instance fields), although it may declare static fields (see Partition I, section 8.4.3).

Similarly, an interface type definition shall not provide implementations for any methods on the values of its type. However, an interface type definition may and usually does define method contracts (method name and method signature) that shall be implemented by supporting types. An interface type definition may define and implement static methods (see Partition I, section 8.4.3), since static methods are associated with the interface type itself rather than with any value of the type.

Interfaces may have static or virtual methods, but shall not have instance methods.

CLS Rule 19: CLS-compliant interfaces shall not define static methods, nor shall they define fields.

NOTE

CLS-compliant interfaces may define properties, events, and virtual methods.

CLS (consumer): Need not accept interfaces that violate these rules.

CLS (extender): Need not provide syntax to author interfaces that violate these rules.

CLS (framework): Shall not externally expose interfaces that violate these rules. Where static methods, instance methods, or fields are required, a separate class may be defined that provides them.


Interface types may also define event and property contracts that shall be implemented by object types that support the interface. Since event and property contracts reduce to sets of method contracts (see Partition I, section 8.6), the above rules for method definitions apply. For more information, see Partition I, sections 8.11.4 and 8.11.3.

Interface type definitions may specify other interface contracts that implementations of the interface type are required to support. See Partition I, section 8.9.8.3 for specifics.

An interface type is given a visibility attribute, as described in Partition I, section 8.5.3, that controls from where the interface type may be referenced. An interface type definition is separate from any object type definition that supports the interface type. Hence, it is possible, and often desirable, to have a different visibility for the interface type and the implementing object type. However, since accessibility attributes are relative to the implementing type rather than the interface itself, all members of an interface shall have public accessibility, and no security permissions may be attached to members or to the interface itself.

8.9.5 Class Type Definition

All types other than interfaces, and those types for which a definition is automatically supplied by the CTS, are defined by class definitions. A class type is a complete specification of the representation of the values of the class type and all of the contracts (class, interface, method, property, and event) that are supported by the class type. Hence, a class type is an exact type. A class definition, unless it specifies that the class is an abstract object type, not only defines the class type; it also provides implementations for all of the contracts supported by the class type.

A class definition, and hence the implementation of the class type, always resides in some assembly. An assembly is a configured set of loadable code modules and other resources that together implement a unit of functionality.

NOTE

While class definitions always define class types, not all class types require a class definition. Array types and pointer types, which are implicitly defined, are also class types. See Partition I, section 8.2.3.


An explicit class definition is used to define:

  • An object type (see Partition I, section 8.2.3).

  • A value type and its associated boxed type (see Partition I, section 8.2.4).

An explicit class definition:

  • Names the class type.

  • Implicitly assigns the class type name to a scope i.e., the assembly that contains the class definition, (see Partition I, section 8.5.2).

  • Defines the class contract of the same name (see Partition I, section 8.6).

  • Defines the representations and valid operations of all values of the class type using member definitions for the fields, methods, properties, and events (see Partition I, section 8.11).

  • Defines the static members of the class type (see Partition I, section 8.11).

  • Specifies any other interface and class contracts also supported by the class type.

  • Supplies implementations for member and interface contracts supported by the class type.

  • Explicitly declares a visibility for the type, either public or assembly (see Partition I, section 8.5.3).

  • May optionally specify a method to be called to initialize the type.

ANNOTATION

"Class types" is a general term that includes both object types and value types. All class types require definitions, except for the built-in types those types that the VES initializes upon their declaration in code. The built-in object types are arrays and pointers. The built-in value types are the numeric types (integers and floating point values).

Class definitions usually have both shape and behavior types and fields and the methods that manipulate them but they may have just one or the other.

One potential source of confusion is the terminology. Whereas class type is used in this standard as the general term for both object and value types, many of the conforming languages, such as C#, Visual Basic.NET, and Java, use class as the keyword for an object type, and struct or structure to designate a value type. Even more confusing, C++ uses the keyword class for a value type.

Another potential confusion in terminology is that C# and Visual Basic.NET both use the keyword object, but only to refer to the single class System.Object, the object root.


The semantics of when, and what triggers execution of such type initialization methods, is as follows:

  1. A type may have a type-initializer method, or not.

  2. A type may be specified as having a relaxed semantic for its type-initializer method (for convenience below, we call this relaxed semantic BeforeFieldInit).

  3. If marked BeforeFieldInit, then the type's initializer method is executed at, or sometime before, first access to any static field defined for that type

  4. If not marked BeforeFieldInit then that type's initializer method is executed at (i.e., is triggered by):

    • First access to any static or instance field of that type, or

    • First invocation of any static, instance, or virtual method of that type

  5. Execution of any type's initializer method will not trigger automatic execution of any initializer methods defined by its base type, nor of any interfaces that the type implements

NOTE

BeforeFieldInit behavior is intended for initialization code with no interesting side-effects, where exact timing does not matter. Also, under BeforeFieldInit semantics, type initializers are allowed to be executed at or before first access to any static field of that Type at the discretion of the CLI.

If a language wishes to provide more rigid behavior e.g., type initialization automatically triggers execution of parent initializers, in a top-to-bottom order, then it can do so by either:

  • defining hidden static fields and code in each class constructor that touches the hidden static field of its parent and/or interfaces it implements, or

  • by making explicit calls to System.Runtime.CompilerServices.Runtime-Helpers.RunClassConstructor (see the .NET Framework Standard Library Annotated Reference).


ANNOTATION

The CLI specifies two ways to initialize types: one with strict rules and guarantees built into it, and the other more flexible and with much higher performance. Type initialization is done with a call to a class constructor (.cctor). This is distinct from an object constructor (.ctor). An object constructor, which sets up the shape of an object, must be called on all CLI objects, but a call to a class constructor (type initialization) is optional. Object construction does not set up the static data in a class, because that is not part of the shape of the object, so most class constructors are called to set up the static fields, although they may do many other things, such as ensuring that another class has been loaded, setting up global variables, etc.

There has been an argument in the programming community about when type initialization should occur. One point of view calls for strict rules, saying that types should be initialized at the first access to any static or instance field of a type, or at the first invocation of a static, instance, or virtual method. These rules provide repeatable behavior in all cases, and some languages, like Java, initialize types according to those rules, so the CLI supports that.

There is, however, a performance cost to doing it that way, especially in JIT-compiled systems. So the CLI offers a more relaxed way to initialize types, with the BeforeFieldInit bit. When this bit is set, the VES will call the class constructor at the first access to the static fields of a type. In this case, instance fields and virtual methods do not make a difference. These rules were chosen as a result of analyzing a large body of code in several different languages, and determining that what most programmers mainly need is the assurance that their static data is initialized before they use it.

The rules for BeforeFieldInit initialization provide high performance, but they do not provide a strong, repeatable guarantee of initialization.

In some languages, as in C#, both of these behaviors are available depending on how you write your program. If you write the code for the initializer in your program, you get the strict semantics for exactly when that initializer is called. If you just use the syntax that specifies the values for static fields, C# uses the more relaxed initialization, guaranteeing that static fields are set up before you call them.

Type initialization is also discussed in Partition II, section 9.5.3 and its subsections.


8.9.6 Object Type Definitions

All objects are instances of an object type. The object type of an object is set when the object is created, and it is immutable. The object type describes the physical structure of the instance and the operations that are allowed on it. All instances of the same object type have the same structure and the same allowable operations. Object types are explicitly declared by a class type definition, with the exception of Array types, which are intrinsically provided by the VES.

8.9.6.1 Scope and Visibility

Since object type definitions are class type definitions, object type definitions implicitly specify the scope of the name of [the] object type to be the assembly that contains the object type definition, see Partition I, section 8.5.2. Similarly, object type definitions shall also explicitly state the visibility attribute of the object type (either public or assembly); see Partition I, section 8.5.3.

8.9.6.2 Concreteness

An object type may be marked as abstract by the object type definition. An object type that is not marked abstract is by definition concrete. Only object types may be declared as abstract. Only an abstract object type is allowed to define method contracts for which the type or the VES does not also provide the implementation. Such method contracts are called abstract methods (see Partition I, section 8.11). All methods on an abstract class need not be abstract.

It is an error to attempt to create an instance of an abstract object type, whether or not the type has abstract methods. An object type that derives from an abstract object type may be concrete if it provides implementations for any abstract methods in the base object type and is not itself marked as abstract. Instances may be made of such a concrete derived class. Locations may have an abstract type, and instances of a concrete type that derives from the abstract type may be stored in them.

ANNOTATION

Concreteness is about whether you can make an instance of the class. If you define a class and provide definitions for all of its methods, and its marked abstract, you still can't make an instance of it. An abstract class cannot be made concrete, therefore. An instance can be made only from a subclass of an abstract class. Therefore, an abstract type is never the exact type of anything. Any instance of a class must have implementations for all methods, and it cannot be marked abstract.


8.9.6.3 Type Members

Object type definitions include member definitions for all of the members of the type. Briefly, members of a type include fields into which values are stored, methods that may be invoked, properties that are available, and events that may be raised. Each member of a type may have attributes as described in Partition I, section 8.4.

  • Fields of an object type specify the representation of values of the object type by specifying the component pieces from which it is composed (see Partition I, section 8.4.1). Static fields specify fields associated with the object type itself (see Partition I, section 8.4.3). The fields of an object type are named and they are typed via location signatures. The names of the members of the type are scoped to the type (see Partition I, section 8.5.2). Fields are declared using a field definition (see Partition I, section 8.11.2).

  • Methods of an object type specify operations on values of the type (see Partition I, section 8.4.2). Static methods specify operations on the type itself (see Partition I, section 8.4.3). Methods are named, and they have a method signature. The names of methods are scoped to the type (see Partition I, section 8.5.2). Methods are declared using a method definition (see Partition I, section 8.11.1).

  • Properties of an object type specify named values that are accessible via methods that read and write the value. The name of the property is the grouping of the methods; the methods themselves are also named and typed via method signatures. The names of properties are scoped to the type (see Partition I, section 8.5.2). Properties are declared using a property definition (see Partition I, section 8.11.3).

  • Events of an object type specify named state transitions in which subscribers may register/unregister interest via accessor methods. When the state changes, the subscribers are notified of the state transition. The name of the event is the grouping of the accessor methods; the methods themselves are also named and typed via method signatures. The names of events are scoped to the type (see Partition I, section 8.5.2). Events are declared using an event definition (see Partition I, section 8.11.4).

ANNOTATION

There is no direct support in the VES for properties and events, except for reflection. At compile time, the compilers specify the methods that perform the property and event functions. As a result, properties and events do not affect VES behavior or shape at all.


8.9.6.4 Supporting Interface Contracts

Object type definitions may declare that they support zero or more interface contracts. Declaring support for an interface contract places a requirement on the implementation of the object type to fully implement that interface contract. Implementing an interface contract always reduces to implementing the required set of methods i.e., the methods required by the interface type.

The different types that the object type implements i.e., the object type and any implemented interface types are each a separate logical grouping of named members. If a class Foo implements an interface IFoo and IFoo declares a member method int a() and the class also declares a member method int a(), there are two members, one in the IFoo interface type and one in the Foo class type. An implementation of Foo will provide an implementation for both, potentially shared.

Similarly, if a class implements two interfaces, IFoo and IBar, each of which defines a method int a(), the class will supply two method implementations, one for each interface, although they may share the actual code of the implementation.

CLS Rule 20: CLS-compliant classes, value types, and interfaces shall not require the implementation of non-CLS-compliant interfaces.

NOTE

CLS (consumer): Need not accept classes, value types or interfaces that violate this rule.

CLS (extender): Need not provide syntax to author classes, value types, or interfaces that violate this rule.

CLS (framework): Shall not externally expose classes, value types, or interfaces that violate this rule.


8.9.6.5 Supporting Class Contracts

Object type definitions may declare support for one other class contract. Declaring support for another class contract is synonymous with object type inheritance (see Partition I, section 8.9.8.1).

ANNOTATION

Supporting class contracts means that with the exception of System.Object itself, all objects have one unique parent type. If a parent implements a method, the child must implement it, although it may do so by doing nothing, and using the parent implementation. By default, the child is given the same implementation as the parent.


8.9.6.6 Constructors

New values of an object type are created via constructors. Constructors shall be instance methods, defined via a special form of method contract, which defines the method contract as a constructor for a particular object type. The constructors for an object type are part of the object type definition. While the CTS and VES ensure that only a properly defined constructor is used to make new values of an object type, the ultimate correctness of a newly constructed object is dependent on the implementation of the constructor itself.

Object types shall define at least one constructor method, but that method need not be public. Creating a new value of an object type by invoking a constructor involves the following steps in order:

  1. Space for the new value is allocated in managed memory.

  2. VES data structures of the new value are initialized, and user-visible memory is zeroed.

  3. The specified constructor for the object type is invoked.

Inside the constructor, the object type may do any initialization it chooses (possibly none).

CLS Rule 21: An object constructor shall call some class constructor of its base class before any access occurs to inherited instance data. This does not apply to value types, which need not have constructors.

CLS Rule 22: An object constructor shall not be called except as part of the creation of an object, and an object shall not be initialized twice.

NOTE

CLS (consumer): Shall provide syntax for choosing the constructor to be called when an object is created.

CLS (extender): Shall provide syntax for defining constructor methods with different signatures. May issue a compiler error if the constructor does not obey these rules.

CLS (framework): May assume that object creation includes a call to one of the constructors, and that no object is initialized twice. System.MemberwiseClone (see the .NET Framework Standard Library Annotated Reference) and deserialization (including object remoting) may not run constructors.


ANNOTATION

This section discusses object instance constructors (.ctor), not class constructors (.cctor), which are discussed in Partition I, section 8.9.5. The CLS, in rules 21 and 22, imposes rules on the constructors that are not imposed by the CLI.


ANNOTATION

The CLI requires that the CIL use the name .ctor for instance constructors, and .cctor for type initializers. Further, to be verifiable, a class constructor must call one of the constructors defined in its parent. Not all languages were enthusiastic about these aspects of the CLI. Among the less enthusiastic were developers of the Eiffel development team. This annotation expresses the Eiffel viewpoint.

Verifiability rules say that to be verifiable, a .ctor constructor should call one of the constructors defined in its parent. The only purpose of this rule is to force a descendant class to initialize fields defined in parent classes. It is basically a lack of trust from the underlying machinery toward the library designer/developer. And it is true, it is very easy to forget to initialize a field. There is also the security aspect and the notion of private fields, which only the parent that is defining them knows how to initialize, as they are not accessible from descendant classes.

However, this is a problem for languages that do not support either this restriction or descendant hiding.

Such is the case with Eiffel. Eiffel does not impose such restrictions because it has the Design by Contract methodology. This methodology enables a parent class to define a behavior. Therefore, if a descendant class does not satisfy this behavior, it is rejected. In the end, it is equivalent to the above CLI design decision, but in Eiffel the library designer/developer is free to reach this goal the way he wants.

Another issue is the requirement of the name .ctor for constructors. Because of this name restriction, it is not possible to create two different instances from the same object for example, point(real a, real b) could be written to produce either polar or Cartesian coordinates, depending on how it was constructed. In Eiffel, a constructor is just a normal routine used for construction purposes. With named constructors, you could have make_polar and make_cartesian as two constructors, each taking two real numbers as arguments. Moreover, having them will make it easier for a language such as Eiffel to map to the CLR model.

Emmanuel Stapf

Microsoft's CLR Architect team agreed that the ability to have independent names for the constructors of objects would have been a better design. Unfortunately, after months of study, it was not possible to find a solution that encompassed the Eiffel design, along with C#, C++, and Java designs.

Jim Miller


8.9.6.7 Finalizers

A class definition that creates an object type may supply an instance method to be called when an instance of the class is no longer accessible. The class System.GC (see the .NET Framework Standard Library Annotated Referencen) provides limited control over the behavior of finalizers through the methods SuppressFinalize and ReRegisterForFinalize. Conforming implementations of the CLI may specify and provide additional mechanisms that affect the behavior of finalizers.

A conforming implementation of the CLI shall not automatically call a finalizer twice for the same object unless

  • There has been an intervening call to ReRegisterForFinalize (not followed by a call to SuppressFinalize), or

  • The program has invoked an implementation-specific mechanism that is clearly specified to produce an alteration to this behavior.

RATIONALE

Programmers expect that finalizers are run precisely once on any given object unless they take an explicit action to cause the finalizer to be run multiple times.


It is legal to define a finalizer for a Value Type. That finalizer, however, will only be run for boxed instances of that Value Type.

ANNOTATION

Finalizers are typically called by the garbage collector (assuming that the VES implements a garbage collector) to close down any allocated resources used by an object when that object goes away. File handles and database connections are the kind of resource you would want to shut down with a finalizer. However, it is more efficient to close out the resource in code when it is no longer needed, because it is expensive to have the GC do it through a finalizer.

Implementation-Specific (Microsoft): In the first release of the Microsoft Common Language Runtime, the behavior of finalizers for Value Types is undefined.


NOTE

Since programmers may depend on finalizers to be called, the CLI should make every effort to ensure that finalizers are called, before it shuts down, for all objects that have not been exempted from finalization by a call to SuppressFinalize. The implementation should specify any conditions under which this behavior cannot be guaranteed.


NOTE

Since resources may become exhausted if finalizers are not called expeditiously, the CLI should ensure that finalizers are called soon after the instance becomes inaccessible. While relying on memory pressure to trigger finalization is acceptable, implementers should consider the use of additional metrics.


8.9.7 Value Type Definition

Not all types defined by a class definition are object types (see Partition I, section 8.2.3); in particular, value types are not object types, but they are defined using a class definition. A class definition for a value type defines both the (unboxed) value type and the associated boxed type (see Partition I, section 8.2.4). The members of the class definition define the representation of both:

  1. When a non-static method (i.e., an instance or virtual method) is called on the value type, its this pointer is a managed reference to the instance, whereas when the method is called on the associated boxed type, the this pointer is an object reference.

    Instance methods on value types receive a this pointer that is a managed pointer to the unboxed type whereas virtual methods (including those on interfaces implemented by the value type) receive an instance of the boxed type.

  2. Value types do not support interface contracts, but their associated boxed types do.

  3. A value type does not inherit; rather the base type specified in the class definition defines the base type of the boxed type.

  4. The base type of a boxed type shall not have any fields.

  5. Unlike object types, instances of value types do not require a constructor to be called when an instance is created. Instead, the verification rules require that verifiable code initialize instances to zero (null for object fields).

ANNOTATION

A class definition for a value causes the VES to define its associated boxed type. The boxed type always inherits from either System.ValueType or System.Enum. They must be sealed, so they cannot have subclasses and do not form a hierarchy. Their shape is fixed.

During the design of the CLI, there was considerable discussion about whether value types should have constructors. There are implementation difficulties, however. You can declare a value type on the stack, and value types can also appear through interactions with unmanaged code in ways that objects can't. When they come from unmanaged code, there is no way to ensure that the constructor has been run. In the end it was decided that it would have been impossible to enforce the construction semantics, so it would be impossible to verify.

However, although value types do not require constructors, they do require initializers to be verifiable.


8.9.8 Type Inheritance

Inheritance of types is another way of saying that the derived type guarantees support for all of the type contracts of the base type. In addition, the derived type usually provides additional functionality or specialized behavior. A type inherits from a base type by implementing the type contract of the base type. An interface type inherits from zero or more other interfaces[, although interface inheritance is not the same as class inheritance. For more information, see Partition I, section 8.9.8.3]. Value types do not inherit, although the associated boxed type is an object type and hence inherits from other types.

The derived class type shall support all of the supported interface contracts, class contracts, event contracts, method contracts, and property contracts of its base type. In addition, all of the locations defined by the base type are also defined in the derived type. The inheritance rules [when followed] guarantee that code that was compiled to work with a value of a base type will still work when passed a value of the derived type. Because of this, a derived type also inherits the implementations of the base type. The derived type may extend, override, and/or hide these implementations.

ANNOTATION

Inheritance rules are described in this section and its subsections. In the International Standard, sections 8.9.8.1 8.9.8.3 are actually numbered (erroneously) 8.9.9 8.9.11. These sections are, in fact, subsections of 8.9.8 and have been renumbered here accordingly. No other subsequent section numbers have been affected.


ANNOTATION

"Derived from" does not equate to "inherits from" in the CLI.

As stated in Partition I, section 8.9.8.2, "value types do not inherit, although the associated boxed type is an object type and hence inherits [from its superclasses]."

The terms "is derived from" and "inherits from" are usually interchangeable; in the CLI, however, they are not interchangeable for value types. (Note: "inherits from" and "is a subclass of" are equivalent terms; also "derived from" and "extends" are equivalent.) The difference affects assignment compatibility.

In the CLI, every type (except System.Object) is derived from a base type. This is represented in IL by the extends keyword. For example, the following IL fragment defines three types: ObjectA, ObjectB, and ValueC:

 
 .class ObjectA extends [mscorlib]System.Object { ... } .class ObjectB extends ObjectA { ... } .class ValueC extends [mscorlib]System.ValueType { ... } 

In the CLI type system, the following relations hold:

Type

Derived From

Inherits From

ObjectB

ObjectA

ObjectA

ValueC

System.ValueType

none

So although object type and value type definitions are syntactically the same, they are semantically different. In the CLI type system, ValueC is derived from System.ValueType.

The consequence of the above is that values of type ObjectB are assignment compatible with variables of type ObjectA. However, values of type ValueC are not assignment compatible with variables of System.ValueType, but those of the corresponding (unnamed) boxed value type are.

Unfortunately, System.Reflection treats value types as though they were the corresponding boxed value types, so the results can be misleading. Here's a C# example:

 

using System; namespace DerivedDemo { class ObjectA { int FieldA; }; class ObjectB : ObjectA { int FieldB; }; struct ValueC { int FieldC; } class Examine { static void SubclassExample() { Type tObjectA = System.Type.GetType("DerivedDemo.ObjectA"); Type tObjectB = System.Type.GetType("DerivedDemo.ObjectB"); bool isObjectAssignable = tObjectA.IsAssignableFrom graphics/ccc.gif(tObjectB); bool isObjectSubclass = tObjectB.IsSubclassOf(tObjectA); Type tValueC = System.Type.GetType("DerivedDemo.ValueC"); Type baseValueC = System.Type.GetType("System.ValueType"); bool isValueAssignable = baseValueC.IsAssignableFrom graphics/ccc.gif(tValueC); bool isValueSubclass = tValueC.IsSubclassOf(baseValueC); ... } } }

Here the variable tValueC represents the type of the value type ValueC. In the code fragment, however, all the Boolean variables are set to the value true, yet according to the CLI type system the last two should be false. The last two are true for the boxed version of ValueC. This difference between System.Reflection and the CLI type system means, for instance, that the IsAssignableFrom method may return true for a given value/variable pair, yet the stind.ref instruction (see Partition III, section 3.62) will fail verification for the same pair.

Nigel Perry


8.9.8.1 Object Type Inheritance

With the sole exception of System.Object, which does not inherit from any other object type, all object types shall either explicitly or implicitly declare support for (inherit from) exactly one other object type. The graph of the inherits relation shall form a singly rooted tree with System.Object at the base; i.e., all object types eventually inherit from the type System.Object.

An object type declares it shall not be used as a base type (be inherited from) by declaring that it is a sealed type.

CLS Rule 23: System.Object is CLS-compliant. Any other CLS-compliant class shall inherit from a CLS-compliant class.


Arrays are object types and as such inherit from other object types. Since array object types are manufactured by the VES, the inheritance of arrays is fixed. See Partition I, section 8.9.1.

ANNOTATION

Many languages strongly urged the Microsoft design team to provide support for multiple inheritance, but the design team rejected the idea for two reasons. The first is that there are three different models of multiple inheritance found in languages. They are not compatible, and in the opinion of the team, no single model was superior to the others. The second reason is that, at the time of this writing, there is no implementation of multiple inheritance in a system that supports dynamic loading that does not sacrifice performance. The feeling of the Microsoft design team was that languages that require multiple inheritance could support it with no performance cost at compile time. The team was unwilling to impose the performance cost on all languages.

Jim Miller


8.9.8.2 Value Type Inheritance

Value Types, in their unboxed form, do not inherit from any type. Boxed value types shall inherit directly from System.ValueType unless they are enumerations, in which case they shall inherit from System.Enum. Boxed value types shall be sealed.

Logically, the boxed type corresponding to a value type

  • Is an object type.

  • Will specify which object type is its base type i.e., the object type from which it inherits.

  • Will have a base type that has no fields defined.

  • Will be sealed to avoid dealing with the complications of value slicing

The more restrictive rules specified here allow for more efficient implementation without severely compromising functionality.

8.9.8.3 Interface Type Inheritance

Interface types may inherit from multiple interface types; i.e., an interface contract may list other interface contracts that shall also be supported. Any type that implements support for an interface type shall also implement support for all of the inherited interface types. This is different from object type inheritance in two ways.

  • Object types form a single inheritance tree; interface types do not.

  • Object type inheritance specifies how implementations are inherited; interface type inheritance does not, since interfaces do not define implementation. Interface type inheritance specifies additional contracts that an implementing object type shall support.

To highlight the last difference, consider an interface, IFoo, that has a single method. An interface, IBar, which inherits from it is requiring that any object type that supports IBar also support IFoo. It does not say anything about which methods IBar itself will have.

8.10 Member Inheritance

Only object types may inherit implementations, hence only object types may inherit members (see Partition I, section 8.9.8). Interface types, while they do inherit from other interface types, only inherit the requirement to implement method contracts, never fields or method implementations.

ANNOTATION

Members are methods, fields, properties, events, and nested types.


8.10.1 Field Inheritance

A derived object type inherits all of the non-static fields of its base object type. This allows instances of the derived type to be used wherever instances of the base type are expected (the shapes, or layouts, of the instances will be the same). Static fields are not inherited. Just because a field exists does not mean that it may be read or written. The type visibility, field accessibility, and security attributes of the field definition (see Partition I, section 8.5.3) determine if a field is accessible to the derived object type.

8.10.2 Method Inheritance

A derived object type inherits all of the instance and virtual methods of its base object type. It does not inherit constructors or static methods. Just because a method exists does not mean that it may be invoked. It shall be accessible via the typed reference that is being used by the referencing code. The type visibility, method accessibility, and security attributes of the method definition (see Partition I, section 8.5.3) determine if a method is accessible to the derived object type.

A derived object type may hide a non-virtual (i.e., static or instance) method of its base type by providing a new method definition with the same name or same name and signature. Either method may still be invoked, subject to method accessibility rules, since the type that contains the method always qualifies a method reference.

Virtual methods may be marked as final, in which case they shall not be overridden in a derived object type. This ensures that the implementation of the method is available, by a virtual call, on any object that supports the contract of the base class that supplied the final implementation. If a virtual method is not final, it is possible to demand a security permission in order to override the virtual method, so that the ability to provide an implementation can be limited to classes that have particular permissions. When a derived type overrides a virtual method, it may specify a new accessibility for the virtual method, but the accessibility in the derived class shall permit at least as much access as the access granted to the method it is overriding. See Partition I, section 8.5.3.

ANNOTATION

Two of the important points in this section should perhaps be emphasized. The first is that methods can be marked as final, which is a feature not available in all systems. The second is that although accessibility may be widened on a method in a subtype, making a method accessible to more things than the base type method, accessibility may not be narrowed.

The accessibility issue was decided after considerable discussion. Although narrowing accessibility would lead you to assume that you are guaranteed that the implementation will not be called, this is not necessarily the case. If you cast the object to the base type and call the virtual method, a virtual call would get you the method without an error because the base type says it's public. Programming languages can implement accessibility as they choose, but the standard does not enforce it.


8.10.3 Property and Event Inheritance

Properties and events are fundamentally constructs of the metadata intended for use by tools that target the CLI and are not directly supported by the VES itself. It is, therefore, the job of the source language compiler and the Reflection Library (see the .NET Framework Standard Library Annotated Reference) to determine rules for name hiding, inheritance, and so forth. The source compiler shall generate CIL that directly accesses the methods named by the events and properties, not the events or properties themselves.

8.10.4 Hiding, Overriding, and Layout

There are two separate issues involved in inheritance. The first is which contracts a type shall implement and hence which member names and signatures it shall provide. The second is the layout of the instance so that an instance of a derived type can be substituted for an instance of any of its base types. Only the non-static fields and the virtual methods that are part of the derived type affect the layout of an object.

The CTS provides independent control over both the names that are visible from a base type (hiding) and the sharing of layout slots in the derived class (overriding). Hiding is controlled by marking a member in the derived class as either hide by name or hide by name-and-signature. Hiding is always performed based on the kind of member; that is, derived field names may hide base field names, but not method names, property names, or event names. If a derived member is marked hide by name, then members of the same kind in the base class with the same name are not visible in the derived class; if the member is marked hide by name-and-signature then only a member of the same kind with exactly the same name and type (for fields) or method signature (for methods) is hidden in the derived class. Implementation of the distinction between these two forms of hiding is provided entirely by source language compilers and the Reflection Library; it has no direct impact on the VES itself.

For example:

 
 class Base { field  int32         A;   field  System.String A;   method int32        A();   method int32         A(int32); } class Derived inherits from Base { field  int32 A;   hidebysig method int32 A(); } 

Table 2-3. Member Names

Kind of Member

Type / Signature of Member

Name of Member

Field

int32

A

Method

() -> int32

A

Method

(int32) -> int32

A

The member names available in type Derived are listed above in Table 2-3.:

While hiding applies to all members of a type, overriding deals with object layout and is applicable only to instance fields and virtual methods. The CTS provides two forms of member overriding, new slot and expect existing slot. A member of a derived type that is marked as a new slot will always get a new slot in the object's layout, guaranteeing that the base field or method is available in the object by using a qualified reference that combines the name of the base type with the name of the member and its type or signature. A member of a derived type that is marked as expect existing slot will reuse (i.e., share or override) a slot that corresponds to a member of the same kind (field or method), name, and type if one already exists from the base type; if no such slot exists, a new slot is allocated and used.

The general algorithm that is used for determining the names in a type and the layout of objects of the type is roughly as follows:

  • Flatten the inherited names (using the hide by name or hide by name-and-signature rule), ignoring accessibility rules.

  • For each new member that is marked "expect existing slot," look to see if an exact match on kind (i.e., field or method), name, and signature exists and use that slot if it is found; otherwise allocate a new slot.

  • After doing this for all new members, add these new member-kind/name/signatures to the list of members of this type.

  • Finally, remove any inherited names that match the new members based on the hide by name or hide by name-and-signature rules.

ANNOTATION

There is further discussion of hiding in Partition II, section 8.3. That discussion explains that a conforming implementation of the CLI treats all references as though the names were marked hide by name-and-signature, and then tells compilers how to get the effect of hide by name. The notion of hiding is a compile-time feature, not a runtime feature.

Overriding concerns behavior. It determines which implementation of a method is expected to be used. In a child class, a method with a given name and signature is supposed to be fulfilling the same contract as the corresponding method in the base class. But circumstances can change. Suppose you create a child class from a base class named Automobile. To that child class, you add a virtual method named Speed. Any class that inherits from your class would, of course, implement the contract of your Speed method.

But in the next version the base class is updated, and a method named Speed is added to it a method that does not have the same function as your method of the same name. This presents a name conflict. The newslot directive prevents a virtual method from being overridden, even if there is another one with the same name in the base type. The advice to compilers is that at compile time, if there is a definition of a new virtual method not seen above in the hierarchy (and so not being inherited), set the newslot bit, which guarantees that no matter what, this method will be seen as a newly introduced virtual method. If the parent class later adds something of the same name, the child's same-name-but-different-contract definition will not be used to implement the parent method.

Without the newslot directive, it is assumed that you were aware of a preceding method in the hierarchy that you are implementing, and your implementation will be used for that method.

Languages choose the way they deal with this. Java, for example, has a specific rule that would forbid setting the newslot directive.


8.11 Member Definitions

Object type definitions, interface type definitions, and value type definitions may include member definitions. Field definitions define the representation of values of the type by specifying the substructure of the value. Method definitions define operations on values of the type and operations on the type itself (static methods). Property and event definitions may only be defined on object types. Property and events define named groups of accessor method definitions that implement the named event or property behavior. Nested type declarations define types whose names are scoped by the enclosing type and whose instances have full access to all members of the enclosing class.

Depending on the kind of type definition, there are restrictions on the member definitions allowed.

8.11.1 Method Definitions

Method definitions are composed of a name, a method signature, and optionally an implementation of the method. The method signature defines the calling convention, type of the parameters to the method, and the return type of the method (see Partition I, section 8.6.1). The implementation is the code to execute when the method is invoked. A value type or object type may define only one method of a given name and signature. However, a derived object type may have methods that are of the same name and signature as its base object type. See Partition I, sections 8.10.2 and 8.10.4.

The name of the method is scoped to the type (see Partition I, section 8.5.2). Methods may be given accessibility attributes (see Partition I, section 8.5.3). Methods may only be invoked with arguments that are assignment compatible with the parameter types of the method signature. The return value of the method shall also be assignment compatible with the location in which it is stored.

Methods may be marked as static, indicating that the method is not an operation on values of the type but rather an operation associated with the type as a whole. Methods not marked as static define the valid operations on a value of a type. When a non-static method is invoked, a particular value of the type, referred to as this or the this pointer, is passed as an implicit parameter.

A method definition that does not include a method implementation shall be marked as abstract. All non-static methods of an interface definition are abstract. Abstract method definitions are only allowed in object types that are marked as abstract.

A non-static method definition in an object type may be marked as virtual, indicating that an alternate implementation may be provided in derived types. All non-static method definitions in interface definitions shall be virtual methods. A virtual method may be marked as final, indicating that derived object types are not allowed to override the method implementation.

ANNOTATION

Whereas non-static methods define valid operations on a value of the type, static methods are operations on the type as a whole. Non-static methods both instance and virtual methods pass an implicit this pointer, which is a reference to the object being called. The programmer does not deal with the implicit this pointer at all. When compilers compile to the Common Intermediate Language (CIL), the this pointer becomes argument 0. The compiler must generate code to figure out which object is being called, and the VES is responsible for making it appear as argument 0. On a virtual call, the VES transfers the this pointer.

For more information, see Partition I, section 8.4.2.


8.11.2 Field Definitions

Field definitions are composed of a name and a location signature. The location signature defines the type of the field and the accessing constraints (see Partition I, section 8.6.1). A value type or object type may define only one field of a given name and type. However, a derived object type may have fields that are of the same name and type as its base object type. See Partition I, sections 8.10.1 and 8.10.4.

The name of the field is scoped to the type (see Partition I, section 8.5.2). Fields may be given accessibility attributes (see Partition I, section 8.5.3). Fields may only store values that are assignment compatible with the type of the field (see Partition I, section 8.3.1).

Fields may be marked as static, indicating that the field is not part of values of the type but rather a location associated with the type as a whole. Locations for the static fields are created when the type is loaded and initialized when the type is initialized.

Fields not marked as static define the representation of a value of a type by defining the substructure of the value (see Partition I, section 8.4.1). Locations for such fields are created within every value of the type whenever a new value is constructed. They are initialized during construction of the new value. A non-static field of a given name is always located at the same place within every value of the type.

ANNOTATION

The last sentence of the previous paragraph may be misleading. It was intended to say that the goal of inheritance is to allow the value of a subtype to be used wherever the value of the parent type is used. There is an implementation issue that says that the simplest way to do subtyping (subclassing) is to create subtypes that have the same shape as the parent initially, with additions at the end. But this is not a requirement of the standard.


A field that is marked serializable is to be serialized as part of the persistent state of a value of the type. This standard does not specify the mechanism by which this is accomplished.

ANNOTATION

The standard completely specifies the file format, and the serializable bit is in the file format, although serialization was not standardized. Implementing serialization would allow such things as remoting, interactive Web services, saving data in persistent format, etc.


8.11.3 Property Definitions

A property definition defines a named value and the methods that access the value. A property definition defines the accessing contracts on that value. Hence, the property definition specifies which accessing methods exist and their respective method contracts. An implementation of a type that declares support for a property contract shall implement the accessing methods required by the property contract. The implementation of the accessing methods defines how the value is retrieved and stored.

A property definition is always part of either an interface definition or a class definition. The name and value of a property definition is scoped to the object type or the interface type that includes the property definition. While all of the attributes of a member may be applied to a property (accessibility, static, etc.), these are not enforced by the CTS. Instead, the CTS requires that the method contracts that comprise the property shall match the method implementations, as with any other method contract. There are no CIL instructions associated with properties, just metadata.

By convention, properties define a getter method (for accessing the current value of the property) and optionally a setter method (for modifying the current value of the property). The CTS places no restrictions on the set of methods associated with a property, their names, or their usage.

CLS Rule 24: The methods that implement the getter and setter methods of a property shall be marked SpecialName in the metadata.

CLS Rule 25: The accessibility of a property and of its accessors shall be identical.

CLS Rule 26: A property and its accessors shall all be static, all be virtual, or all be instance.

CLS Rule 27: The type of a property shall be the return type of the getter and the type of the last argument of the setter. The types of the parameters of the property shall be the types of the parameters to the getter and the types of all but the final parameter of the setter. All of these types shall be CLS-compliant, and shall not be managed pointers (i.e., shall not be passed by reference).

CLS Rule 28: Properties shall adhere to a specific naming pattern. See Partition I, section 10.4. The SpecialName attribute referred to in CLS rule 24 shall be ignored in appropriate name comparisons and shall adhere to identifier rules.

NOTE

CLS (consumer): Shall ignore the SpecialName bit in appropriate name comparisons and shall adhere to identifier rules. Otherwise, no direct support other than the usual access to the methods that define the property.

CLS (extender): Shall ignore the SpecialName bit in appropriate name comparisons and shall adhere to identifier rules. Otherwise, no direct support other than the usual access to the methods that define the property. In particular, an extender need not be able to define properties.

CLS (framework): Shall design understanding that not all CLS languages will access the property using special syntax.


8.11.4 Event Definitions

The CTS supports events in precisely the same way that it supports properties (see Partition I, section 8.11.3). The conventional methods, however, are different and include means for subscribing and unsubscribing to events as well as for firing the event.

CLS Rule 29: The methods that implement an event shall be marked SpecialName in the metadata.

CLS Rule 30: The accessibility of an event and of its accessors shall be identical.

CLS Rule 31: The add and remove methods for an event shall both either be present or absent.

CLS Rule 32: The add and remove methods for an event shall each take one parameter whose type defines the type of the event and that shall be derived from System.Delegate.

CLS Rule 33: Events shall adhere to a specific naming pattern. See Partition I, section 10.4. The SpecialName attribute referred to in CLS rule 29 shall be ignored in appropriate name comparisons and shall adhere to identifier rules.

NOTE

CLS (consumer): Shall ignore the SpecialName bit in appropriate name comparisons and shall adhere to identifier rules. Otherwise, no direct support other than the usual access to the methods that define the event.

CLS (extender): Shall ignore the SpecialName bit in appropriate name comparisons and shall adhere to identifier rules. Otherwise, no direct support other than the usual access to the methods that define the event. In particular, an extender need not be able to define events.

CLS (framework): Shall design based on the understanding that not all CLS languages will access the event using special syntax.


8.11.5 Nested Type Definitions

A nested type definition is identical to a top-level type definition, with one exception: a top-level type has a visibility attribute, while the visibility of a nested type is the same as the visibility of the enclosing type. See Partition I, section 8.5.3.

ANNOTATION

The previous statement is not accurate. The visibility of top-level types and the accessibility of nested types are specified using three bits in the TypeDef flags, specified in Partition II, section 22.1.14, Flags for Types (TypeAttributes). For top-level types, the only legal values of these three bits are 0 (NotPublic, sometimes called internal) or 1 (Public). For nested types, they must be 2 through 7, indicating one of six possible accessibility values.




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