今天开始有空开启一篇基础的.net core学习笔记
最近两年微软大力发展.net core,看重了跨平台与开源的潜力,是时候学习一波.net core的基础了。
基础概念:
基础概念:CLR的组成部分:
IL是.net中间代码的称呼,学习中间代码对.net能够有更加深入的了解。
学习方法:对比C#代码与生成好的IL代码
学习工具:反编译代码,我用的是dotpeek。
举例:C#代码如下:
using System;
namespace develop
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}
1.首先依赖的程序集:System.Console,System.Runtime
2.定义的程序集与程序集相关属性:
3.定义程序集模块,与模块中的元数据:包含了各种类型定义,方法定义等。
4.类型定义IL代码如下:
// Type: develop.Program
// Assembly: develop, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
// MVID: C8D04018-0EE5-4568-B55E-992F5FC9C40D
// Location: D:\develop\bin\Debug\net5.0\develop.dll
// Sequence point data from D:\develop\bin\Debug\net5.0\develop.pdb
.class private auto ansi beforefieldinit
develop.Program
extends [System.Runtime]System.Object
{
省略部分代码
}
.class private auto ansi beforefieldinit:
5.方法定义
.method private hidebysig static void
Main(
string[] args
) cil managed
{
.entrypoint
.maxstack 8
// [8 9 - 8 10]
IL_0000: nop
// [9 13 - 9 47]
IL_0001: ldstr "Hello World!"
IL_0006: call void [System.Console]System.Console::WriteLine(string)
IL_000b: nop
// [10 9 - 10 10]
IL_000c: ret
} // end of method Program::Main
.method public hidebysig specialname rtspecialname instance void
.ctor() cil managed
{
.maxstack 8
IL_0000: ldarg.0 // this
IL_0001: call instance void [System.Runtime]System.Object::.ctor()
IL_0006: nop
IL_0007: ret
} // end of method Program::.ctor
主要注意的关键字:
格式组成:标签 操作 参数
例子: IL_001:(标签) ldc.i4.(操作) 1(参数)
功能:将常量1放入评价堆栈
IL中有许多类似的操作命令,其中有个曾经困惑我的fld相关的指令,比如:ldfld。意思是:从评价堆栈取出一个值,读取这个值的指定字段。
解惑:结合了非静态方法会利用ldarg,将方法的第一个参数放入评价堆栈,非静态方法第一个参数是this,指向对象,ldfld会从评价堆栈取出这个对象,然后读取他的字段值。
测试代码:
using System;
namespace dotnet_test
{
class Program
{
static void Main(string[] args)
{
var obj = new MyClass();
Console.WriteLine("Hello World!");
Console.WriteLine(obj.testIncrement());
}
}
class MyClass
{
private int testCount;
public int testIncrement()
{
return ++testCount;
}
}
}
对应的IL代码:
.class private auto ansi beforefieldinit
dotnet_test.MyClass
extends [System.Runtime]System.Object
{
.field private int32 testCount
.method public hidebysig instance int32
testIncrement() cil managed
{
.maxstack 3
.locals init (
[0] int32 V_0,
[1] int32 V_1
)
// [21 9 - 21 10]
IL_0000: nop
// [22 13 - 22 32]
IL_0001: ldarg.0 // this
IL_0002: ldarg.0 // this
IL_0003: ldfld int32 dotnet_test.MyClass::testCount
IL_0008: ldc.i4.1
IL_0009: add
IL_000a: stloc.0 // V_0
IL_000b: ldloc.0 // V_0
IL_000c: stfld int32 dotnet_test.MyClass::testCount
IL_0011: ldloc.0 // V_0
IL_0012: stloc.1 // V_1
IL_0013: br.s IL_0015
// [23 9 - 23 10]
IL_0015: ldloc.1 // V_1
IL_0016: ret
} // end of method MyClass::testIncrement
有几个问题:
1.为啥会调用两次 ldarg.0就是将非静态方法的第一关参数(this,注释里面也有标注出来)放入评价堆栈。为啥会有两次呢,因为++testCount,首先会从this中读取一次testCount值,然后add指令之后,将结果会再次存入this的testCount中去。至于为啥这么智能知道先就调用两次将this放进去评价堆栈呢,这个就是.net的编辑器牛逼之处了,再书的最后两章有给出解释。
2.它调用ldfld怎么知道是哪个字段呢,我们再加一个字段进去看看,后面的int32 dotnet_test.MyClass::testCount指定了字段的完整路径
class MyClass
{
private int testCount;
private int testCountA;
public int testIncrement()
{
testCountA++;
return ++testCount;
}
}
.method public hidebysig instance int32
testIncrement() cil managed
{
.maxstack 3
.locals init (
[0] int32 V_0,
[1] int32 V_1
)
// [22 9 - 22 10]
IL_0000: nop
// [23 13 - 23 26]
IL_0001: ldarg.0 // this
IL_0002: ldarg.0 // this
IL_0003: ldfld int32 dotnet_test.MyClass::testCountA
IL_0008: ldc.i4.1
IL_0009: add
IL_000a: stfld int32 dotnet_test.MyClass::testCountA
// [24 13 - 24 32]
IL_000f: ldarg.0 // this
IL_0010: ldarg.0 // this
IL_0011: ldfld int32 dotnet_test.MyClass::testCount
IL_0016: ldc.i4.1
IL_0017: add
IL_0018: stloc.0 // V_0
IL_0019: ldloc.0 // V_0
IL_001a: stfld int32 dotnet_test.MyClass::testCount
IL_001f: ldloc.0 // V_0
IL_0020: stloc.1 // V_1
IL_0021: br.s IL_0023
// [25 9 - 25 10]
IL_0023: ldloc.1 // V_1
IL_0024: ret
} // end of method MyClass::testIncrement
从上面可以看到指定的字段,包含字段类型,命名空间,类名,字段名。
IL命令具体可以查看微软的官方文档:链接地址
此外,需要理解评价堆栈,是一个栈结构,先进后出,市面上很多代码执行器,都会用这种方式使得指令之间能够互相交互值。特别指出,如果想要自己写一个代码执行器(比如:qyscript,ILRuntime)都可以参考这种方式。
再则就是注意方法内的本地变量的定义如下:
.locals init (
[0] int32 V_0,
[1] int32 V_1
)
可以看到C#代码中++testCount,并没有使用任何临时变量,但是IL代码中会自动根据需要,增加本地变量的定义,++testCount,利用本地变量进行add指令操作,注意返回值是如何通过本地变量返回的。
using System;
namespace dotcore_test
{
class Program
{
static void Main(string[] args)
{
var obj = new MyClass();
obj.Test = "5555";
Console.WriteLine("Hello World!");
Console.WriteLine(obj.Test);
}
}
class MyClass
{
public string Test { get; set; }
}
}
/ Type: dotcore_test.MyClass
// Assembly: dotcore_test, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
// MVID: 9AD8FB1B-C055-4CB0-B5AE-701BCCD0F0FB
// Location: E:\dotcore_test\bin\Debug\net5.0\dotcore_test.dll
// Sequence point data from E:\dotcore_test\bin\Debug\net5.0\dotcore_test.pdb
.class private auto ansi beforefieldinit
dotcore_test.MyClass
extends [System.Runtime]System.Object
{
.field private string '<Test>k__BackingField'
.custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor()
= (01 00 00 00 )
.custom instance void [System.Runtime]System.Diagnostics.DebuggerBrowsableAttribute::.ctor(valuetype [System.Runtime]System.Diagnostics.DebuggerBrowsableState)
= (01 00 00 00 00 00 00 00 ) // ........
// int32(0) // 0x00000000
.method public hidebysig specialname instance string
get_Test() cil managed
{
.custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor()
= (01 00 00 00 )
.maxstack 8
// [18 30 - 18 34]
IL_0000: ldarg.0 // this
IL_0001: ldfld string dotcore_test.MyClass::'<Test>k__BackingField'
IL_0006: ret
} // end of method MyClass::get_Test
.method public hidebysig specialname instance void
set_Test(
string 'value'
) cil managed
{
.custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor()
= (01 00 00 00 )
.maxstack 8
// [18 35 - 18 39]
IL_0000: ldarg.0 // this
IL_0001: ldarg.1 // 'value'
IL_0002: stfld string dotcore_test.MyClass::'<Test>k__BackingField'
IL_0007: ret
} // end of method MyClass::set_Test
.method public hidebysig specialname rtspecialname instance void
.ctor() cil managed
{
.maxstack 8
IL_0000: ldarg.0 // this
IL_0001: call instance void [System.Runtime]System.Object::.ctor()
IL_0006: nop
IL_0007: ret
} // end of method MyClass::.ctor
.property instance string Test()
{
.get instance string dotcore_test.MyClass::get_Test()
.set instance void dotcore_test.MyClass::set_Test(string)
} // end of property MyClass::Test
} // end of class dotcore_test.MyClass
属性是C#中比较特别的,可以将其看做特别的方法。
属性定义:
.property instance string Test()
{
.get instance string dotcore_test.MyClass::get_Test()
.set instance void dotcore_test.MyClass::set_Test(string)
} // end of property MyClass::Test
指明get与set方法对应的实际方法
.net编译器转换C#等代码为IL代码,CLR执行时将其转换为对应的机器码,这样能够做到跨平台。学习IL代码相关知识,能够更好的帮助我们认识具体底层的机制与原理。