An interface is a reference type, in spite of the fact that it has no code at all. Thus, wecannot instantiate an interface. We can use it as a construct for the creation of new types.An interface defines a contract that is left to the class to implement.An interface can have static fields. If an interface contains 10 abstract virtual functions,then the class implementing from that interface has to supply the code for all 10 of them.Thus, if a class does not provide all the function implementations, then we cannot use theclass. In such a scenario, a class derived from it must provide the implementation.The interface keyword in C# is a class, which the documentation describes as a semanticattribute.
We are not allowed to place any code in an interface. An interface consists only of thefunction prototype, followed by a pair of curly braces {}
vijay1 is a function created in the interface yyy. As this is not permitted, the il assemblerhas the domino effect as shown above
No variables either can be placed inside an interface. This rule is similar to that of C#.Even though the documentation says that we can place static fields in an interface, whenwe tried to do so, an error was generated.
If we create an object such as an interface using newobj, the assembler does not generateany error, but the runtime throws an exception
The above program has only one function, a1 in the interface. This function has a pair ofcurly braces {}, but as mentioned earlier, we are not allowed to place any code within them.The class zzz has been derived from yyy, using the keyword implements. The assemblerdoes not check whether all code of the interface is implemented as it is done only atruntime, and hence, an exception is generated. Thus, we can see that most IL errors occurat run time, and not at compile time
To reiterate what we have said earlier, an interface in C# becomes a class directive in ILwith the interface modifier added to it. The two functions a1 and a2 become actualfunctions in the class ddd, and are marked as virtual, newslot as well as abstract, i.e.having no implementation
We cannot place any code, including a ret, in a method that is marked as abstract. Thismodifier signifies that the code for the function will be provided from some other source.Inspite of what the documentation says, a static constructor cannot be placed in aninterface.
For the purposes of inheritance, C# does not differentiate between an interface and a class.There is, however, a subtle difference between them in a sense that, we can derive frommore than one interface, but not from more than one class.In IL, there is a marked differentiation between an interface and a class. We extend from a
class and implement an interface. This is the same syntax that the Java programminglanguage uses.When one compares C# with Java, their support for features such as these should behighlighted
We created two locals that look like class yyy and interface ddd. Then, we created an objectthat looks like yyy and initialized the variable V_0 to it.The statement d =a is translated to: loading the value of V_0 on the stack, and usinginstruction stloc.1 to initialize the variable V_1. Thereafter, calling the function a1 of theinterface ddd.We loaded the variable value V_1 on the stack. Since it was called through the interface, weused callvirt instead of call. If we had called it through the object of type yyy, then wewould have used call.Thus, IL understands that a call through an interface object is to be treated in a specialmanner. We can change the last occurrence of ldloc.1 to ldloc.0, since both have the samevalues. A call to an interface is evaluated at run time, as the assembler does not convert itinto a class access. In the locals directive, the word class, and not interface, is placed infront of ddd.Thus, calling a function through an interface is equivalent to using callvirt since, there isno code in the interface. A callvirt takes more time to execute than the plain callinstruction. However, callvirt introduces dynamism at runtime.
We have created a class yyy that is derived from two interfaces, ddd and eee, that have thesame function a1.Since we want a separate implementation for each, we have to preface each occurrence ofa1 with the name of the interface, i.e. either ddd or eee, in the class yyy.We have created the two objects that look like yyy and stored them in classes that look likeddd and eee. Since the function is called from an interface pointer, we have to use callvirtinstead of call. In the IL code, the two interfaces are created as shown earlier.In class yyy, we implement from ddd and eee, but since the two functions cannot have thesame name, we have to preface the name of the function with the name of the interface.
In the method, we have used a directive called .override. This directive clearly specifies asto which function from a specified interface the function override. Calling of an interface isa run time issue. The CLR does all the routine work.
The above program clears a large number of cobwebs. Let us start analysing this programfrom the beginning.We have a class interface ddd that has two functions a1 and a2. We then create a classyyy, that implements from ddd and contains code for only one function a1.This makes the class incomplete and hence, we tag it with the modifier abstract. However,this modifier is optional. One more class xxx is created, that derives from yyy andimplements the second function a2. All goes well so far.Then, using call, the function a2 of class xxx is called. However, when we call the samefunction off the interface ddd using callvirt, the function is called off class yyy and not xxx.This is so as the function in class xxx has nothing to do with the one in class yyy.Compare this example with the override modifier example shown earlier. If we eliminate thecode of function a2 from the class yyy, we get the following error:
The function a2 is present in the class xxx, but it has been eliminated from the class yyy.However, the function needs to be present in both the classes.The same rules of nesting apply to interfaces also. Nothing stops an interface fromimplementing another interfaces, using the keyword implements. Here, the wordimplements may be misleading.The keyword implies that the class that implements this interface must provide the codefor it.
An interface has five restrictions:i. All methods must be either virtual or static.ii. The virtual methods must be abstract and public.iii. No instance fields are allowed.iv. An interface is abstract and cannot be instantiated.v. An interface cannot inherit from a class.vi. Under no circumstances can an interface contain code.
A structure handles memory more efficiently than a class. IL does not support a struct typedirectly. As IL does not recognise a structure, it does not enforce the following rules:• Constructors must have parameters• All members of a structure must be initialised before leaving the constructor.Also, structures are derived from ValueType and not Object.The type system of the .Net world is simplicity personified. It divides all the known typesinto one of the two categories: a value type or a reference type.• A reference type is known by a reference, that is, a memory location that stores theaddress where the object resides in memory.• A value type, however, is directly stored in the memory location occupied by thevariable that represents the type.Value types are used to represent small data items like local variables, integers, numberswith decimal places etc. The memory allocated is on the stack and not on the heap.To access a reference type, the location of the variable in memory is to be first determined.This is not true for a value type. Hence, there is no overhead of an indirection involved witha value type and therefore, it is much more efficient.The disadvantage of a value type is that they cannot be derived from and, if the data theyrepresent is fairly large, then copying the type on the stack is not an efficient way ofrepresenting that type. There is no need to instantiate a variable of value type, as it isalready instantiated. Apart from these variations, value types are similar to reference types.
In the above example, we have created a value class or a value type called xxx, that hastwo public fields i and j. We used the instruction initobj to create a new value type.To display the value of i, we first created a local variable that represents our value class. Inour case, the variable is called v. ldloca is used to load the address of the variable v on thestack. Then we called initobj with the name of the value class xxx as a parameter thuscreating a a new value type.We, then, again load the address of the value type v on the stack and call ldfld. Thisinstruction needs the address of the value type on the stack to work with. The only reasonthat the value of i is ZERO is that the instruction initobj guarantees that all members ofthe value type will be initialized to zero.
The correct way to initialize a value class is to call the constructor. We first have to loadthe address of the value type v on the stack. Then, since the constructor expects a singleparameter on the stack, we place the number 2 on the stack using ldc. The constructor isthen called in the same manner as we call any other function.In the constructor, we first place the this pointer on the stack. The this pointer, or the firstinvisible parameter to a function, is a reference to the starting location of the object inmemory. Parameter 1 is placed on the stack and stfld is called.The constructor initializes all members of a value class. The static fields of a value classare initialized when the value type is first loaded.
Not using initobj, like in the above example will assign a random value to the value type.The use of initobj is optional. This instruction requires a managed pointer to an instance ofthe value type and it is one of the few instructions that does not return anything on thestack. The constructor is never called by the initobj instruction. The sole role initobjperforms is to initialize all the value class members to ZERO.While verifying code, one should ensure that all the fields of a value type are assigned avalue before they are read or passed as parameters to a method. The code in constructorassigns values to every field.You can see the contrast between initobj and newobj. Value Types use initobj whereasreference types use newobj. Also, value types are derived from System.ValueType.Value types can have static, instance and virtual methods. Here, the static methods arecalled in a similar manner when in a class.
To call an instance function of a value class, there is no need for either initobj or theconstructor call. But, it is a good practice to do so. We have to place the address of thevalue type or the this pointer on the stack and then place the parameters. The function a1uses the this pointer to access the fields.We modified the function abc to read as follows:.method public virtual instance void a1(int32 p) il managedDespite making the function virtual, the program executes as before. The order of thevirtual modifier is very important.You may recall that a virtual function has to be called using the instruction callvirt and notthe instruction call. However, in the case of a value class, we cannot use callvirt. Instead,the instruction call is used.
In the above program, we specified an interface ddd, that contains a single function calleda1. We created a value class xxx that implements from ddd.Our intention is to call the function a1 from the interface ddd. As mentioned earlier, to calla function off an interface, the instruction callvirt has to be used and not the instructioncall, as, an interface does not contain any code.The callvirt instruction requires a reference type on the stack because it does not workwith value types. Thus, we use ldloca to load the address of the value type on the stack.Then, we use the box instruction to convert it into a reference type.
If we comment out the box instruction, the following exception is generated because callvirtlooks for a a boxed type on the stack:
The constructor assigns values to fields. It places the values on the stack and uses stfld toassign the values to the fields. The question that arises is that what happens when weequate reference objects with each other.The explanation is very simple: A reference object is simply a memory location stored in alocal variable. The variable V_0 contains a reference to the newly created object in memory.We place this value on the stack and use ldloc.1 to initialize the variable V_1 to this value.Thus, a reference object is a number representing the memory location of an object. Here,the same number is stored in the objects a and b. Hence b.i displays the number 10. Here,the constructor does not get called again, as no new object is created.
This program and the next one should be read in conjunction if you want to grasp thefollowing:• Concepts of boxing and unboxing• The major difference between a class and a structure.The concept of a structure is not supported by IL. On conversion to IL, a struct becomes aclass with the modifier value added to it. It is sealed and derived from ValueType hencereferred to as a value class.In the C# program, an object is created, which is an instance of the structure xxx. Theconstructor is passed the constant value 1, which is used to initialize the int field x to 1.Then, the object b is initialized to a. Next, we change the value of the member x from 1 to 2using the value object a.
We display the value of the field x using b. The cast operator is used as the data type of bis Object and not xxx. We notice that there are two x ints in memory:• One with a value of 2 that is associated with a,• The other one with the value of 1 that is associated with b.So much for the C# program, let us see as to what happens in our IL program. We create 3objects in IL i.e. two variables V_1 and V_2 of the class xxx and one that looks like anObject.We place the address of V_0 on the stack followed by the value 1. Then, we call theconstructor using a call and not newobj, since we have a value class or structure and not apure class. The constructor initializes the field x to 1.We have to convert this value class to a pure object that is an instance of the class Object.We load the address of V_O and call the box instruction, which converts a value class intoa class and places the reference of the newly created object on the stack.Then, we store this reference in the local variable V_1 using stloc.1. This is the codegenerated when the statement object b = a is converted to IL. We have created a freshobject using the box instruction. Thus there are two xxx objects in memory, one as thevalue object V_0 and one as a reference object V_1.We now need to initialize the field x to 2. To do so, the constant 2 is placed on the stackand stfld is called. The easier part of the code is over.The problem is in the expression WriteLine((xxx)b).x. The object b or V_1 is a referenceobject. We have to cast it to a value object. To do this, we need to unbox it. The act ofconverting a reference object to a value object is called unboxing. The unbox instructionrequires a reference type on the stack and it will place a value type whose data type isspecified by the name following xxx.The instruction ldobj loads an instance of xxx on the stack whose pointer is alreadypresent on the stack. We store this instance in V_2 and load this value type again on thestack. Then we load the value of x and display it using WriteLine.
c is an object of type yyy and holds a value of 1 in its member x. Object d is another classor call it a structure, it does not create a new object in memory but instead, points to thesame object referenced by c. Thus, we have one yyy object in memory, and any changesmade to the value of x using d will be reflected when using c and vice-versa.Here, as we are dealing with a class, the instruction newobj is used to create it. To initializethe object d to c, we first use ldloc.0 to place its value on the stack and then use theinstruction stloc.1 to initialize local V_1.Then, we initialize c.x to 2 in the usual manner, by first placing the reference on the stackusing ldloc.0 and then, placing the value on the stack using stfld.Object d is already a reference object and yyy is a class. Hence we simply use castclass. Itis easy to use casting here because neither boxing nor unboxing is required to be carriedout.The important point to be mentioned is that we are not creating another yyy in memory,and hence, there is only one field x in memory. This was not the case earlier case, when astructure was used.
This example is deceptively similar to the one above.First we take a reference object b and equate it to a value object f. Then we cast thereference type object b to a value type int. The C# compiler gives us no errors but aruntime exception is thrown.Back to IL. We first create a long or an int64 V_0 using locals. Then we create an ObjectV_1 and finally an int V_2.We thereafter, place 1 on the stack, convert it into 8 bytes using conv.i8 and use stloc.0 tostore the value in V_0. The address is then placed on the stack, as we need to use the boxinstruction to convert it into a reference type, which is finally to be stored in b or V_1.Unbox the object b, the one created out of a value type, and store in an int. To do this, weneed to place a reference on the stack and call unbox. This will place a value address onthe stack and use ldind.i4 to fetch the value stored at this address. Then, we use stloc.2 toinitialize the variable V_2. The exception clearly states that we cannot cast an object that isa reference type to a value type.
Languages decide on how you write code and name variables. In C# a field and a parameterto a function can have the same names, but the parameter name has more visibility thanthe field name.this.x refers to the field name in the function abc unlike the parameter named x. In IL, thisdilemma does not arise, as we have one set of instructions that deals with fields, a secondset that deals with parameters to functions and a third set that deals with locals. Thusthere is no way that a name clash can ever occur.
We take one more program on structures before we close this chapter. . We have created astruct containing a field i and a function abc. Structures are value objects and are storedon the stack and not on the heap. Thus, the word value has been used in the localsdirective. It is a class, but of a value type.In the definition of the structure, we have added two modifiers, sealed and value
Therefore, we cannot derive from this value class. Everything else is similar to a class.