Static, Instance, Virtual Methods

Static, Instance, Virtual Methods

We can classify methods in many ways: global methods vs. member methods, variable argument lists vs. constant argument lists, and so on. Global and vararg methods are discussed in later sections. In this section, we’ll focus on static vs. instance methods. Take a look at the diagram shown in Figure 9-2.

Static methods are shared by all instances of a type. They don’t require an instance pointer (this) and cannot access instance members unless the instance pointer is provided explicitly. When a type is loaded, static methods are placed in a separate typewide table.

Figure 9-2 Method classification.

The signature of a static method is exactly as it is specified, with the first specified argument being number 0:

.method public static void Bar(int32 i, float32 r) {    ldarg.0 // Load int32 i on stack     }

Instance methods are instance-specific and have the this instance pointer as an unlisted first (number 0) argument of the signature:

.method public instance void Bar(int32 i, float32 r) {    ldarg.0 // Load instance pointer on stack    ldarg.1 // Load int32 i on stack     }

note

Be careful about the use of the keyword instance in specifying the method calling convention. When a method is defined, its flags—including the static flag—are explicitly specified. Because of this, at definition time it’s not necessary to specify an instance calling convention—it can be inferred from the presence or absence of the static flag. When a method is referenced, however, its flags are not specified, so in this case the instance keyword must be specified explicitly for instance methods; otherwise, the referenced method is presumed static. This creates a seeming contradiction: a method when declared is instance by default (no static flag specified), and the same method when referenced is static by default (no instance specified). But static is a flag and instance is a calling convention, so in fact we’re dealing with two different default options here.

Instance methods are divided into virtual and nonvirtual methods, identified by the presence or absence of the virtual flag. The virtual methods of a class are called through the virtual method table (v-table) of this class, which adds another level of indirection to implement so-called late binding. Virtual methods can be overridden in derived classes by their own virtual methods of the same signature and name—and even of a different name, although such overriding requires an explicit declaration, as described later in this chapter. Virtual methods can be abstract or might offer some implementation.

If you have a nonvirtual method declared in a class, it does not mean that you can’t declare another nonvirtual method with the same name and signature in a class derived from the first one. You can, but it will be a different method, having nothing to do with the method declared in the base class. Such a method in the derived class hides the respective method in the base class, but the hidden method can still be called if you explicitly specify the owning class.

If you do the same with virtual methods, however, the method declared in the derived class actually replaces, or overrides, the method declared in the base class. This is true unless, of course, you specify the newslot flag on the overriding method, in which case the overriding method will occupy a new entry of the v-table and hence will not really be overriding anything.

To illustrate this point, take a look at the following code from the sample file Virt_not.il on the companion CD:

.class public A {    .method public specialname void .ctor() { ret }    .method public void Foo()    {       ldstr "A::Foo"       call void [mscorlib]System.Console::WriteLine(string)       ret    }    .method public virtual void Bar()    {       ldstr "A::Bar"       call void [mscorlib]System.Console::WriteLine(string)       ret    }    .method public virtual void Baz()    {       ldstr "A::Baz"       call void [mscorlib]System.Console::WriteLine(string)       ret    } } .class public extends A {    .method public specialname void .ctor() { ret }    .method public void Foo()    {       ldstr "B::Foo"       call void [mscorlib]System.Console::WriteLine(string)       ret    }    .method public virtual void Bar()    {       ldstr "B::Bar"       call void [mscorlib]System.Console::WriteLine(string)       ret    }    .method public virtual newslot void Baz()    {       ldstr "B::Baz"       call void [mscorlib]System.Console::WriteLine(string)       ret    } } .method public static void Exec() {    .entrypoint    newobj instance void B::.ctor() // Create instance of derived class    castclass class A           // Cast it to base class    dup                 // We need 3 instance pointers    dup                 // On stack for 3 calls    call instance void A::Foo()    callvirt instance void A::Bar()    callvirt instance void A::Baz()    ret}

If we compile and run the sample, we’ll receive this output:

A:Foo B:Bar A:Baz

Because the method A::Foo is nonvirtual, declaring B::Foo does not affect A::Foo in any way. So when we cast B to A and call A::Foo, B::Foo does not enter the picture—it’s a different method.

Because the A::Bar method is virtual, as is B::Bar, when we create an instance of B, B::Bar replaces A::Bar in the v-table. Casting B to A after that does not change anything: B::Bar is sitting in the v-table of the class instance, and A::Bar is gone. So when we call A::Bar, the “usurper” B::Bar is called instead.

Both the A::Baz and B::Baz methods are virtual, but B::Baz is marked newslot. Thus, instead of replacing A::Baz in the v-table, B::Baz takes a new entry and peacefully coexists with A::Baz. Since A::Baz is still present in the v-table of the instance, the situation is practically (oops, almost wrote “virtually”; should watch it; can’t have puns in such a serious book) identical to the situation with A::Foo and B::Foo, except that the calls are done through the v-table. The Microsoft Visual Basic .NET compiler likes this concept and uses it rather extensively.

If we don’t want a virtual method to be overridden in the class descendants, we can mark it with the final flag. If you try to override a final method, the loader fails and throws a TypeLoad exception.

Unboxed value types don’t have v-tables. It is perfectly legal to declare the virtual methods as members of a value type, but these methods can be called only from a boxed instance of the value type:

.class public value XXX {    .method public void YYY( )    {           }    .method public virtual void ZZZ( )    {           } } .method public static void Exec( ) {    .locals init(valuetype XXX xxx) // Variable xxx is an                                     // Instance of XXX     ldloca xxx                      // Load managed ptr to xxx    call instance void XXX::YYY( )  // Legal: access to value                                     // Type member                                     // By managed ptr    ldloca xxx    callvirt instance void XXX::ZZZ( ) // Illegal: access to virtual                                     // Methods possible only                                     // By object reference.    ldloc xxx                       // Load instance of XXX.    box   valuetype XXX             // Convert it to object reference.    callvirt instance void XXX::ZZZ( )   // Legal     }



Inside Microsoft. NET IL Assembler
Inside Microsoft .NET IL Assembler
ISBN: 0735615470
EAN: 2147483647
Year: 2005
Pages: 147
Authors: SERGE LIDIN

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