C# to IL 6 Reference and Value Types(引用类型和值类型)
An interface is a reference type, in spite of the fact that it has no code at all. Thus, we
cannot 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 the
class. 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 semantic
attribute.
We are not allowed to place any code in an interface. An interface consists only of the
function prototype, followed by a pair of curly braces {}
vijay1 is a function created in the interface yyy. As this is not permitted, the il assembler
has 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, when
we tried to do so, an error was generated.
If we create an object such as an interface using newobj, the assembler does not generate
any error, but the runtime throws an exception
The above program has only one function, a1 in the interface. This function has a pair of
curly 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 assembler
does not check whether all code of the interface is implemented as it is done only at
runtime, and hence, an exception is generated. Thus, we can see that most IL errors occur
at run time, and not at compile time
To reiterate what we have said earlier, an interface in C# becomes a class directive in IL
with the interface modifier added to it. The two functions a1 and a2 become actual
functions 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. This
modifier 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 an
interface.
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 from
more 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 programming
language uses.
When one compares C# with Java, their support for features such as these should be
highlighted
We created two locals that look like class yyy and interface ddd. Then, we created an object
that 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 using
instruction stloc.1 to initialize the variable V_1. Thereafter, calling the function a1 of the
interface ddd.
We loaded the variable value V_1 on the stack. Since it was called through the interface, we
used callvirt instead of call. If we had called it through the object of type yyy, then we
would have used call.
Thus, IL understands that a call through an interface object is to be treated in a special
manner. We can change the last occurrence of ldloc.1 to ldloc.0, since both have the same
values. A call to an interface is evaluated at run time, as the assembler does not convert it
into a class access. In the locals directive, the word class, and not interface, is placed in
front of ddd.
Thus, calling a function through an interface is equivalent to using callvirt since, there is
no code in the interface. A callvirt takes more time to execute than the plain call
instruction. However, callvirt introduces dynamism at runtime.
We have created a class yyy that is derived from two interfaces, ddd and eee, that have the
same function a1.
Since we want a separate implementation for each, we have to preface each occurrence of
a1 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 like
ddd and eee. Since the function is called from an interface pointer, we have to use callvirt
instead 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 the
same 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 as
to which function from a specified interface the function override. Calling of an interface is
a run time issue. The CLR does all the routine work.
The above program clears a large number of cobwebs. Let us start analysing this program
from the beginning.
We have a class interface ddd that has two functions a1 and a2. We then create a class
yyy, 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 and
implements 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 same
function 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 the
code 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 from
implementing another interfaces, using the keyword implements. Here, the word
implements may be misleading.
The keyword implies that the class that implements this interface must provide the code
for 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 type
directly. 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 types
into 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 the
address where the object resides in memory.
• A value type, however, is directly stored in the memory location occupied by the
variable that represents the type.
Value types are used to represent small data items like local variables, integers, numbers
with 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 with
a 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 they
represent is fairly large, then copying the type on the stack is not an efficient way of
representing that type. There is no need to instantiate a variable of value type, as it is
already 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 has
two 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. In
our case, the variable is called v. ldloca is used to load the address of the variable v on the
stack. Then we called initobj with the name of the value class xxx as a parameter thus
creating a a new value type.
We, then, again load the address of the value type v on the stack and call ldfld. This
instruction needs the address of the value type on the stack to work with. The only reason
that the value of i is ZERO is that the instruction initobj guarantees that all members of
the value type will be initialized to zero.
The correct way to initialize a value class is to call the constructor. We first have to load
the address of the value type v on the stack. Then, since the constructor expects a single
parameter on the stack, we place the number 2 on the stack using ldc. The constructor is
then 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 first
invisible parameter to a function, is a reference to the starting location of the object in
memory. 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 class
are 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 of
the value type and it is one of the few instructions that does not return anything on the
stack. The constructor is never called by the initobj instruction. The sole role initobj
performs 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 a
value before they are read or passed as parameters to a method. The code in constructor
assigns values to every field.
You can see the contrast between initobj and newobj. Value Types use initobj whereas
reference types use newobj. Also, value types are derived from System.ValueType.
Value types can have static, instance and virtual methods. Here, the static methods are
called 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 the
constructor call. But, it is a good practice to do so. We have to place the address of the
value type or the this pointer on the stack and then place the parameters. The function a1
uses 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 managed
Despite making the function virtual, the program executes as before. The order of the
virtual modifier is very important.
You may recall that a virtual function has to be called using the instruction callvirt and not
the 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 called
a1. 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 call
a function off an interface, the instruction callvirt has to be used and not the instruction
call, as, an interface does not contain any code.
The callvirt instruction requires a reference type on the stack because it does not work
with 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 callvirt
looks for a a boxed type on the stack:
The constructor assigns values to fields. It places the values on the stack and uses stfld to
assign the values to the fields. The question that arises is that what happens when we
equate reference objects with each other.
The explanation is very simple: A reference object is simply a memory location stored in a
local 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 the
following:
• 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 a
class with the modifier value added to it. It is sealed and derived from ValueType hence
referred to as a value class.
In the C# program, an object is created, which is an instance of the structure xxx. The
constructor 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 2
using the value object a.
We display the value of the field x using b. The cast operator is used as the data type of b
is 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 3
objects in IL i.e. two variables V_1 and V_2 of the class xxx and one that looks like an
Object.
We place the address of V_0 on the stack followed by the value 1. Then, we call the
constructor using a call and not newobj, since we have a value class or structure and not a
pure 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 into
a 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 code
generated when the statement object b = a is converted to IL. We have created a fresh
object using the box instruction. Thus there are two xxx objects in memory, one as the
value 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 stack
and 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 reference
object. We have to cast it to a value object. To do this, we need to unbox it. The act of
converting a reference object to a value object is called unboxing. The unbox instruction
requires a reference type on the stack and it will place a value type whose data type is
specified by the name following xxx.
The instruction ldobj loads an instance of xxx on the stack whose pointer is already
present on the stack. We store this instance in V_2 and load this value type again on the
stack. 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 class
or call it a structure, it does not create a new object in memory but instead, points to the
same object referenced by c. Thus, we have one yyy object in memory, and any changes
made 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 initialize
the object d to c, we first use ldloc.0 to place its value on the stack and then use the
instruction 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 stack
using 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. It
is easy to use casting here because neither boxing nor unboxing is required to be carried
out.
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 a
structure 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 the
reference type object b to a value type int. The C# compiler gives us no errors but a
runtime exception is thrown.
Back to IL. We first create a long or an int64 V_0 using locals. Then we create an Object
V_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 to
store the value in V_0. The address is then placed on the stack, as we need to use the box
instruction 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, we
need to place a reference on the stack and call unbox. This will place a value address on
the stack and use ldind.i4 to fetch the value stored at this address. Then, we use stloc.2 to
initialize the variable V_2. The exception clearly states that we cannot cast an object that is
a reference type to a value type.
Languages decide on how you write code and name variables. In C# a field and a parameter
to a function can have the same names, but the parameter name has more visibility than
the field name.
this.x refers to the field name in the function abc unlike the parameter named x. In IL, this
dilemma does not arise, as we have one set of instructions that deals with fields, a second
set that deals with parameters to functions and a third set that deals with locals. Thus
there 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 a
struct containing a field i and a function abc. Structures are value objects and are stored
on the stack and not on the heap. Thus, the word value has been used in the locals
directive. 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.
C# to IL 6 Reference and Value Types(引用类型和值类型)的更多相关文章
- Emit学习(2) - IL - 值类型和引用类型(补)
上周末回家去享受生活了, 工作是为了更好的生活嘛, 所以我把生活, 工作分的比较开. 这几天不是很忙, 在学习工作技能的同时, 发点博文, 也算是做一个学习笔记 上篇中, 贴出的地址里面那位哥, 也有 ...
- C#中的值类型(value type)与引用类型(reference type)的区别
ylbtech- .NET-Basic:C#中的值类型与引用类型的区别 C#中的值类型(value type)与引用类型(reference type)的区别 1.A,相关概念返回顶部 C#中 ...
- 7.1 Backup and Recovery Types 备份和恢复类型
7.1 Backup and Recovery Types 备份和恢复类型 这个章节描述 不同备份类型的特点: 物理(raw)与逻辑备份 物理备份有raw 副本组成,存储数据库内容,这种类型的备份是适 ...
- CLR via C# 3rd - 05 - Primitive, Reference, and Value Types
1. Primitive Types Any data types the compiler directly supports are called primitive types. ...
- 5.Primitive, Reference, and Value Types
1.Programming Language Primitive Types primitive types:Any data types the compiler directly supports ...
- Kotlin Reference (四) Basic Types
most from reference 基本类型 在kotlin中,一切都是对象,我们可以在任何变量上调用成员函数和属性.一些类型可以具有特殊的内部表示:例如,数字.字符和布尔值都可以在运行时被表示为 ...
- java.sql.Types,数据库字段类型,java数据类型的对应关系
以下转自:http://kummy.itpub.net/post/17165/172850 本文在原文基础上有增减. 本概述是从<JDBCTM Database Access from Java ...
- [Effective Modern C++] Item 6. Use the explicitly typed initializer idiom when auto deduces undesired types - 当推断意外类型时使用显式的类型初始化语句
条款6 当推断意外类型时使用显式的类型初始化语句 基础知识 当使用std::vector<bool>的时候,类型推断会出现问题: std::vector<bool> featu ...
- 函数类型(Function Types):函数类型和其他类型一样
函数类型(Function Types) 每个函数都有种特定的函数类型,由函数的参数类型和返回类型组成. 例如: 这个例子中定义了两个简单的数学函数:addTwoInts 和 multiplyTwoI ...
随机推荐
- session和cokkie的区别与作用
session在计算机中,尤其是在网络应用中,称为“会话机制”,Session对象存储特定用户会话所需的属性及配置信息,这样,当用户在应用程序的web页之间跳转时,存储在session对象中的变量将不 ...
- spring 配置 applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.spr ...
- mybatis mapper配置文件 CustomerMapper.xml
Dao @Repositorypublic interface CustomerDAO { public void create(CustomerModel cm); public voi ...
- 3-D crustal model transfer to cdl format
The downloaded crustal model file, for example, its name is TW-PS-H14.nc The command is ncdump -b c ...
- matlab中循环的使用
转载自 https://blog.csdn.net/ssure/article/details/30329601 matlab 中的while循环只有 while statement .... end ...
- 《深入分析Java Web技术内幕》读书笔记 - 第1章 深入Web请求过程
第1章 深入Web请求过程 1 1.1 B/S网络架构概述 2 基于统一的应用层协议HTTP来交互数据. 1.2 如何发起一个请求 4 HTTP连接本质是建立Socket连接.请求实现方式:工具包如H ...
- 用Filter实现图片防盗链
首先继承自FilterAttribute类同时实现IActionFilter接口,代码如下: //// <summary> /// 防盗链Filter. /// </summary& ...
- Beta阶段冲刺---Day2
一.Daily Scrum Meeting照片 二.今天冲刺情况反馈 1.昨天已完成的工作· 题目切换的改进· 支持退格操作 2.今天计划完成的工作· 数字以扑克牌的形式给出· 答案的乘除符号与游戏中 ...
- apache .htacess
htaccess 详解 .htaccess是什么 .htaccess文件(或者"分布式配置文件")提供了针对目录改变配置的方法, 即,在一个特定的文档目录中放置一个包含一个或多 ...
- HTTP Methods 和 RESTful Service API 设计
含义: HTTP Methods:也叫 HTTP Verbs,HTTP Methods 可以翻译成 HTTP 方法.它们是 HTTP 协议的一部分,主要规定了 HTTP 如何请求和操作服务器上的资源, ...