Complex Tasks

Until now, the focus has been on individual instructions. Most programs consist of complex tasks, such as creating a new class, creating an array, or executing a for loop. Complex tasks typically comprise multiple instructions.

Managing Types

Classes contain static and instance members. The static members are accessible through the class name, whereas instance members are bound to an object. The WriteLine method is a static method. As demonstrated in previous sample code, WriteLine is called directly on the Console class (for example, System.Console::WriteLine).

Instance members require an object. The newobj instruction creates an object (an instance of a class) and then invokes the constructor to initialize the members of the object. It also deposits a reference to the object onto the evaluation stack. The reference then can be used to call a member method or to access a member field. Such an action consumes the reference and removes it from the evaluation stack. Several actions might require several references. The dup instruction is useful for duplicating a reference or whatever happens to be on the top of the evaluation stack.

The following shows two versions of the syntax for the newobj instruction:

newobj instance ctorsignature :

newobj ctorsignature

The instance signature in the preceding syntax calls an instance constructor.

Constructors are specially named methods. The name of a constructor is ctor, and a constructor is declared with the .ctor directive. By convention in C#, constructors return void, which is enforced by the C# compiler. In MSIL code, a constructor can return a value. Static constructors are named cctor and are declared with the identically named directive. The static constructor is called by the CLR when the class or instance is first accessed.

The .field directive adds a field to the class. The ldfld and ldsfld instructions load an instance and static field onto the evaluation stack, respectively. Conversely, the stfld and stsfld instructions store data from the evaluation stack into a field. The stfld and stsfld instructions consume a reference to the related object. These instructions have a single operand, which is the field type of the related object on the evaluation stack.

The following program creates a class that contains an instance field and a static field and in which the instance and static constructors initialize the fields. The AddField and SubtractField methods return the total and difference of the fields. An instance of the object is created in the Main method. The resulting reference is duplicated with the dup instruction. Why? Both the AddFields and SubtractFields methods are called, each of which requires a reference. The box instruction is explained in the section "Boxing," later in this chapter.

.assembly extern mscorlib {}
.assembly application {}

.namespace Donis.CSharpBook {

    .class Starter {

        .method static public void Main() il managed {
            .entrypoint
            .locals (int32 temp)
            newobj instance void Donis.CSharpBook.ZClass::.ctor()
            dup
            call instance int32 Donis.CSharpBook.ZClass::AddFields()
            stloc.0
            ldstr "The total is {0}"
            ldloc.0
            box int32
            call void [mscorlib] System.Console::WriteLine(string, object)
            call instance int32 Donis.CSharpBook.ZClass::SubtractFields()
            stloc.0
            ldstr "The difference is {0}"
            ldloc.0
            box int32
            call void [mscorlib] System.Console::WriteLine(string, object)
            ret
        }
    }

    .class ZClass {

        .method private hidebysig specialname rtspecialname
                static void .cctor() cil managed {
            ldstr "In static constructor"
            call void [mscorlib] System.Console::WriteLine(string)
            ldc.i4.s 10
            stsfld int32 Donis.CSharpBook.ZClass::fielda
            ret
        }

        .method public hidebysig specialname rtspecialname
                instance void .ctor() cil managed {
            ldstr "In constructor"
            call void [mscorlib] System.Console::WriteLine(string)
            ldarg.0
            ldc.i4.s 5
            stfld int32 Donis.CSharpBook.ZClass::fieldb
            ret
        }

        .method public int32 AddFields() cil managed {
            ldsfld int32 Donis.CSharpBook.ZClass::fielda
            ldarg.0
            ldfld int32 Donis.CSharpBook.ZClass::fieldb
            add
            ret
        }

        .method public int32 SubtractFields() cil managed {
            ldsfld int32 Donis.CSharpBook.ZClass::fielda
            ldarg.0
            ldfld int32 Donis.CSharpBook.ZClass::fieldb
            sub
            ret
        }

        .field static private int32 fielda
        .field private int32 fieldb
    }
}

In the preceding code, the ldarg.0 instruction appears near the beginning of each method. This instruction places the this reference to the current object on the evaluation stack.

Boxing

The previous sample code included the box instruction. Here is the code snippet:

ldstr "The total is {0}"
ldloc.0
box int32
call void [mscorlib] System.Console::WriteLine(string, object)

The box instruction prepares a value type argument for the Console::WriteLine method. The Console::WriteLine method has two parameters, which are both reference types. However, the top of the evaluation stack has a value type and a reference type. The assignment of a value to a reference type is the problem. The memory models are inconsistent. The box instruction removes the value type from the evaluation stack, creates an object on the managed heap that boxes the value type, and places a reference to the newly created object back on the evaluation stack. Now that the value type has been replaced with a reference type, Console::WriteLine can be called successfully.

The unbox instruction works in reverse. It unboxes a reference that is on the evaluation stack to the specified value type. The reference is replaced on the evaluation stack with the unboxed value.

Here is the syntax of the box and unbox instructions:

box valuetype

unbox valuetype

Inheritance

In previous examples in this chapter, no class inherited directly from another class. The classes implicitly inherited System.Object. Most classes can be inherited explicitly, including System.Object, by using the extends keyword. The child class will inherit most members of the base class, except constructors. In the base class, methods that are expected to be overridden in the child should be prefixed with the keyword virtual. The callvirt instruction calls the overridden function in a child. A child instance should be on the evaluation stack before using callvirt.

The following sample code demonstrates inheritance in MSIL code. Two classes are defined in the example. ZClass is an abstract class. XClass is a concrete class that inherits ZClass. MethodA and MethodB are implemented in both classes. In ZClass, MethodA is not virtual, but MethodB is virtual. The four methods are stubbed with console messages. In Main, an instance of XClass is created. Then both MethodA and MethodB are called through ZClass to demonstrate polymorphism. Although both MethodA and the virtual MethodB are called using the same calling syntax, the ZClass implementation of MethodA is executed but the XClass implementation of MethodB is executed:

.assembly extern mscorlib {}
.assembly application {}

.namespace Donis.CSharpBook {

    .class Starter {

        .method static public void Main() il managed {
            .entrypoint
            newobj instance void Donis.CSharpBook.XClass::.ctor()
            dup
            callvirt instance void Donis.CSharpBook.ZClass::MethodA()
            callvirt instance void Donis.CSharpBook.ZClass::MethodB()
            ret
        }
    }

    .class abstract ZClass {

        .method public instance void MethodA() il managed {
            ldstr "ZClass::MethodA"
            call void [mscorlib] System.Console::WriteLine(string)
            ret
        }

        .method public virtual instance void MethodB() il managed {
            ldstr "ZClass::MethodB"
            call void [mscorlib] System.Console::WriteLine(string)
            ret
        }
    }

    .class XClass extends Donis.CSharpBook.ZClass {

        .method public specialname rtspecialname
            instance void .ctor() cil managed {
            ret
        }

        .method public instance void MethodA() il managed {
            ldstr "XClass::MethodA"
            call void [mscorlib] System.Console::WriteLine(string)
            ret
        }

        .method public virtual instance void MethodB() il managed {
            ldstr "XClass::MethodB"
            call void [mscorlib] System.Console::WriteLine(string)
            ret
        }
    }
}

Interfaces

MSIL can define an interface. There is no interface directive; instead, add the interface keyword to the specifics of the class directive. The interface keyword enforces the semantics of an interface type on the class. Member methods of the interface must be public, abstract, and virtual. Fields are not allowed in an interface class. In addition, constructors and destructors are not permitted. The ILASM compiler enforces these and other rules for interfaces.

A class uses the implements keyword to list interfaces that must be implemented. If there are multiple interfaces, the interface list is comma-delimited. A derived class must implement all members of those interfaces. This code demonstrates creating an interface and an implementation of the interface:

.assembly extern mscorlib {}
.assembly application {}

.namespace Donis.CSharpBook {

    .class interface public abstract IA {
        .method public abstract virtual instance void MethodA() il managed {
        }
    }

    .class Starter {
        .method static public void Main() il managed {
            .entrypoint
            newobj instance void Donis.CSharpBook.ZClass::.ctor()
            callvirt instance void Donis.CSharpBook.ZClass::MethodA()
            ret
        }
    }

    .class ZClass implements Donis.CSharpBook.IA {
        .method public specialname rtspecialname instance void .ctor() cil managed {
            ret
        }

        .method public virtual instance void MethodA() il managed {
            ldstr "ZClass:MethodA"
            call void [mscorlib] System.Console::WriteLine(string)
            ret
        }
    }
}

MethodA is defined as part of interface IA and is an abstract function. In C#, abstract functions do not have a function body. In MSIL code, however, abstract methods have a body, but the body cannot contain an implementation. Essentially, an abstract method has a body but no code.

Structures

To define a structure, declare a type with the class directive and add the value keyword to the class detail. The semantics of a structure then are enforced by the MSIL compiler. For example, a structure cannot have an explicit default constructor or destructor, a structure is sealed, and so on. The value keyword is implemented by the ILASM compiler as an implicit inheritance of System.ValueType. You could drop the value keyword and inherit System.ValueType directly. The compiler also adds keywords required for a structure, such as the sealed keyword.

As a value type, structures are defined as local variables with the .locals directive. Value types are stored on the stack, not on the managed heap. Accessing a member, such as calling a member method, requires binding to the address of the structure. You must load the address of the structure onto the evaluation stack before accessing a member. To do so, you need the address of the structure. To load the address of a local variable, use the ldloca instruction instead of ldloc. (The "a" variation of an MSIL instruction refers to an address.)

Call the constructor of a structure explicitly with the call instruction, not the newobj instruction. Structures are not created on the heap. When the constructor is called directly, the structure is simply initialized but not placed on the managed heap.

This code creates and initializes a structure:

.assembly extern mscorlib {}
.assembly application {}

.namespace Donis.CSharpBook {

    .class Starter {

        .method static public void Main() il managed {
            .entrypoint
            .locals init (valuetype Donis.CSharpBook.ZStruct obj)
            ldloca.s obj
            ldc.i4.s 10
            call instance void Donis.CSharpBook.ZStruct::.ctor(int32)
            ldloca.s obj
            ldfld int32 Donis.CSharpBook.ZStruct::fielda
            call void [mscorlib]System.Console::WriteLine(int32)
            ret
        }
    }

    .class value ZStruct {
        .method public specialname rtspecialname
                instance void .ctor(int32) cil managed {
            ldarg.0
            ldarg.1
            stfld int32 Donis.CSharpBook.ZStruct::fielda
            ret
        }

        .field public int32 fielda
    }
}
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
52.14.172.93