.get_Item方法的汇编代码,最后得出结果是“毫无二 致”。由于汇编代码和机器代码一一对应,因此观察汇编代码就可以完全了解 CPU 是如何执行这两个方法的。汇编代码一模一样,就意味着 CPU 对待这两个方法 的方式一模一样,它们的性能怎么会有不同呢?
结论:.NET 的 Object 泛型容器的性能不会低于直接使用 Object 的容器,因为 CLR 在处理 Object 泛型的时候,会生成与直接使用 Object 类型时一模一样的类型,因 此性能是不会降低的。但是您是通过学习 IL 可以了解这些吗?显然不是,如果您只是学习了 IL ,最终还是要“听别人说”才能知道这些,而即使您不学 IL ,在 “听别人说”了之后您也了解了这些 ——同时也不会因为不了解 IL 而变得“易忘”等等。
同样道理, IL 的 call 指令和 callvirt 指令的区别是什么呢?“别人会告诉你” call 指令直接就去调用了那个方法,而 callvirt 还需 要去虚方法表里去“寻找”那个真正的方法;“别人可能还会告诉你”,查找虚方法是靠方法表地址加偏移量;《 Essential .NET 》还会将方法表的实现结构告诉给你,而这些都是 IL 不会告诉您的。您就算了解再多 IL ,也不如“别人告诉你”的这些来得重要。您要了解“别人告诉 你”的东西,也不需要了解多少 IL 。
示例二:只有经过调用的方法才能获得其汇编代码吗?
许多资料都告诉我们,在一个方法被第一次调用之前,它是不会被JIT 的。也就是说,直到第一次调用时它才会被转化为机器码。不过,这个真是这样吗?我们还是准备一段简单的 C# 代码:
namespace TestConsole
{
class Program
{
[MethodImpl(MethodImplOptions.NoInlining)]
private static void SomeMethod()
{
Console.WriteLine("Hello World!");
}
static void Main(string[] args)
{
Console.WriteLine("Before JITed.");
Console.ReadLine();
SomeMethod();
Console.WriteLine("After JITed");
Console.ReadLine();
}
}
}
那么Main 方法的 IL 代码是怎么样的呢?
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
.maxstack 8
// 分配字符串 "Before JITed"
L_0000: ldstr "Before JITed."
// 调用 Console.WriteLine 方法
L_0005: call void [mscorlib]System.Console::WriteLine(string)
// 调用 Console.ReadLine 方法
L_000a: call string [mscorlib]System.Console::ReadLine()
L_000f: pop
// 调用 Program.SomeMethod 方法
L_0010: call void TestConsole.Program::SomeMethod()
// 分配字符串 "After JITed"
L_0015: ldstr "After JITed"
// 调用 Console.WriteLine 方法
L_001a: call void [mscorlib]System.Console::WriteLine(string)
// 调用 Console.ReadLine 方法
L_001f: call string [mscorlib]System.Console::ReadLine()
L_0024: pop
L_0025: ret
}
IL 代码多容易懂呀,这段 IL 代码基本上就和我们的 C# 一样。没错,这就是 IL 的作用。 IL 和 C# 一样,都是用于表现程序逻辑。 C# 使 用 if...else 、 while 、 for 等等丰富语法,而在 IL 中就会变成判断 + 跳转语句。但是,您从一段几十行的 IL 语句中,看出一句十几行 的 while 逻辑——收获在哪里?除此之外, C# 分配一个变量, IL 也分配一个。 C# 调用一个方法, IL 就 call 或 callvirt 一下。 C# 里 new 一个, IL 中就 newobj 一下(自然也会有一些特殊,例如可以使用 jmp 或 tail call 一个方法——是为尾递归,但也只是及其特殊的情况)。可以发现 IL 的功能大部分就是 C# 可以表现的功能。而 C# 隐藏掉的一些细节,在 IL 这里同样 没有显示出来!
那么我们又该如何发现一些细节呢?例如“书本”告诉我们的JIT 的工作方式:方法第一次调用之后才会生成机器码。
这段程序会打印三行文字,在打印出 Before JITed 和 After JITed 字样之后都会有一次停止,需要用户按回车之后才能继续。在进行试验的时候,您可以在程序暂停的时候使用 WinDbg 的 File - Attach to Process 命令附加到 TestConsole.exe 进程中,或者在两次暂停时各生成一个 dump 文件,这样便可不断地重现一些过程。否则的话,应用 程序两次启动所生成的地址很可能会完全不同——因为 JIT 的工作是动态的,有时候很难提前把握。
好,我们已经进入了第一个Console.ReadLine 暂停,在点击回车继续下去之前。我们先使用 WinDbg 进行调试。以下是 Main 方法的汇编代码:
0:000> !name2ee *!TestConsole.Program
Module: 70f61000 (mscorlib.dll)
--------------------------------------
Module: 00172c5c (TestConsole.exe)
Token: 0x02000002
MethodTable: 00173010
EEClass: 001712d0
Name: TestConsole.Program
0:000> !dumpmt -md 00173010
EEClass: 001712d0
Module: 00172c5c
Name: TestConsole.Program
mdToken: 02000002 (.../bin/Release/TestConsole.exe)
BaseSize: 0xc
ComponentSize: 0x0
Number of IFaces in IFaceMap: 0
Slots in VTable: 7
--------------------------------------
MethodDesc Table
Entry MethodDesc JIT Name
71126ab0 70fa4944 PreJIT System.Object.ToString()
71126ad0 70fa494c PreJIT System.Object.Equals(System.Object)
71126b40 70fa497c PreJIT System.Object.GetHashCode()
71197540 70fa49a0 PreJIT System.Object.Finalize()
0017c019 00173008 NONE TestConsole.Program..ctor()
0017c011 00172ff0 NONE TestConsole.Program.SomeMethod()
003e0070 00172ffc JIT TestConsole.Program.Main(System.String[])
0:000> !u 003e0070
Normal JIT generated code
TestConsole.Program.Main(System.String[])
Begin 003e0070, size 4d
>>> 003e0070 55 push ebp
003e0071 8bec mov ebp,esp
*** WARNING: Unable to verify checksum for mscorlib.ni.dll
003e0073 e8a8d3da70 call mscorlib_ni+0x22d420 (7118d420) (System.Console.get_Out(), ...)
003e0078 8bc8 mov ecx,eax
003e007a 8b153020d102 mov edx,dword ptr ds:[2D12030h] ("Before JITed.")
003e0080 8b01 mov eax,dword ptr [ecx]
003e0082 ff90d8000000 call dword ptr [eax+0D8h]
003e0088 e8971b2571 call mscorlib_ni+0x6d1c24 (71631c24) (System.Console.get_In(), ...)
003e008d 8bc8 mov ecx,eax
003e008f 8b01 mov eax,dword ptr [ecx]
003e0091 ff5064 call dword ptr [eax+64h]
003e0094 ff15f82f1700 call dword ptr ds:[172FF8h] (TestConsole.Program.SomeMethod(), ...)
003e009a e881d3da70 call mscorlib_ni+0x22d420 (7118d420) (System.Console.get_Out(), ...)
003e009f 8bc8 mov ecx,eax
003e00a1 8b153420d102 mov edx,dword ptr ds:[2D12034h] ("After JITed")
003e00a7 8b01 mov eax,dword ptr [ecx]
003e00a9 ff90d8000000 call dword ptr [eax+0D8h]
003e00af e8701b2571 call mscorlib_ni+0x6d1c24 (71631c24) (System.Console.get_In(), ...)
003e00b4 8bc8 mov ecx,eax
003e00b6 8b01 mov eax,dword ptr [ecx]
003e00b8 ff5064 call dword ptr [eax+64h]
003e00bb 5d pop ebp
003e00bc c3 ret
请关注上面那个被标红的call 语句,它的含义是:
先从读取172FF8 地址中的值,这才是方法调用的目标地址(即 SomeMethod 方法)。
使用call 指令调用刚才读取到的目标地址
那么在第一次调用SomeMethod 方法之前,目标地址的指令是什么呢?
0:000> dd 172FF8
00172ff8 0017c011 71030002 00200006 003e0070
00173008 00060003 00000004 00000000 0000000c
00173018 00050011 00000004 711d0770 00172c5c
00173028 0017304c 001712d0 00000000 00000000
00173038 71126ab0 71126ad0 71126b40 71197540
00173048 0017c019 00000080 00000000 00000000
00173058 00000000 00000000 00000000 00000000
00173068 00000000 00000000 00000000 00000000
0:000> !u 0017c011
Unmanaged code
0017c011 b000 mov al,0
0017c013 eb08 jmp 0017c01d
0017c015 b003 mov al,3
0017c017 eb04 jmp 0017c01d
0017c019 b006 mov al,6
0017c01b eb00 jmp 0017c01d
0017c01d 0fb6c0 movzx eax,al
0017c020 c1e002 shl eax,2
0017c023 05f02f1700 add eax,172FF0h
0017c028 e9d7478c00 jmp 00a40804
这是什么,不像是 SomeMethod 的内容阿, SomeMethod 是会调用 Console.WriteLine 方法的,怎么变成了一些跳转了呢?于 是我们想起书本(例如《 CLR via C# 》)中的话来,在方法第一次调用时,将会跳转到 JIT 的指令处,对方法的 IL 代码进行编译。再想想书中的示意图,于是恍然大悟,原来这段代码的作用 是 “让 JIT 编译 IL ”啊。那么在 JIT 后,同样的调用会产生什么结果呢?
我们在 WinDbg 中 Debug - Detach Debuggee ,让程序继续运行。单击回车,您会发现屏幕上出现了 Hello Word 和 After JIT 的字样。于是我们继续 Attach to Process ,重复上面的命令。由于 Main 方法已经被编译好了,它的汇编代码不会改变,因此在调用 SomeMethod 方法时的步骤还是不变:先去内 存 172FF8 中读取目标地址,再 call 至目标地址。
0:000> dd 172FF8
00172ff8 003e00d0 71030002 00200006 003e0070
00173008 00060003 00000004 00000000 0000000c
00173018 00050011 00000004 711d0770 00172c5c
00173028 0017304c 001712d0 00000000 00000000
00173038 71126ab0 71126ad0 71126b40 71197540
00173048 0017c019 00000080 00000000 00000000
00173058 00000000 00000000 00000000 00000000
00173068 00000000 00000000 00000000 00000000
0:000> !u 003e00d0
Normal JIT generated code
TestConsole.Program.SomeMethod()
Begin 003e00d0, size 1a
>>> 003e00d0 55 push ebp
003e00d1 8bec mov ebp,esp
*** WARNING: Unable to verify checksum for mscorlib.ni.dll
003e00d3 e848d3da70 call mscorlib_ni+0x22d420 (7118d420) (System.Console.get_Out(), mdToken: 06000772)
003e00d8 8bc8 mov ecx,eax
003e00da 8b153820d102 mov edx,dword ptr ds:[2D12038h] ("Hello World!")
003e00e0 8b01 mov eax,dword ptr [ecx]
003e00e2 ff90d8000000 call dword ptr [eax+0D8h]
003e00e8 5d pop ebp
003e00e9 c3 ret
于是我们发现,虽然步骤没有变,但是由于地址172FF8 中的值改变了,因此 call 的目标也变了。新的目标中包含了 SomeMethod 方法的 IL 代码编译后的机器码,而我们现在看到便是这个机器码的汇编表现形式。
示例三:泛型方法是为每个类型各生成一份代码吗?
IL 和我们平时用的 C# 程序代码不一样,其中使用了各种指令,而不是像 C# 那样有类似于英语的关键字,甚至是语法。但是有一点是类似的,它的主要目的是 表现程序逻辑,而他们表现得逻辑也大都是相同的,接近的。你创建对象那么我也创建,你调用方法那么我也调用。因此才可以有 .NET Reflector 帮我们把 IL 反编译为比 IL 更高级的 C# 代码。如果 IL 把太多细节都展开了,把太多信息都丢弃了,那么怎么可以如此容易就恢复呢?例 如,您可以把一篇 Word 文章转化为图片,那么又如何才能把图片再转回为 Word 格式呢? C => 汇编、汇编 => C ,此类例子数不胜数。
再举一个例子,例如您有以下的范型方法:
private static void GenericMethod()
{
Console.WriteLine(typeof(T));
}
static void Main(string[] args)
{
GenericMethod();
GenericMethod();
GenericMethod();
GenericMethod();
GenericMethod();
GenericMethod();
Console.ReadLine();
}
有朋友认为,范型会造成多份代码拷贝。那么您是否知道,使用不同的范型类型去调用GenericMethod 方法,会各生成一份机器码吗?我们先看一下 IL 吧:
.method private hidebysig static void GenericMethod() cil managed
{
.maxstack 8
L_0000: ldtoken !!T
L_0005: call class [mscorlib]System.Type
[mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
L_000a: call void [mscorlib]System.Console::WriteLine(object)
L_000f: ret
}
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
.maxstack 8
L_0000: call void TestConsole.Program::GenericMethod()
L_0005: call void TestConsole.Program::GenericMethod()
L_000a: call void TestConsole.Program::GenericMethod()
L_000f: call void TestConsole.Program::GenericMethod()
L_0014: call void TestConsole.Program::GenericMethod()
L_0019: call void TestConsole.Program::GenericMethod()
L_001e: ret
}
这……怎么和我们的C# 代码如此接近。嗯,谁让 IL 清清楚楚明明白白地知道什么叫做“泛型”,于是直接使用这个特性就可以了。所以我们还是用别的办法吧。
其实要了解CLR 是否为每个不同类型生成了一份新的机器码,只要看看汇编中是否每次都 call 到同一个地址中去便可以了。用相同的方法可以看到 Main 方法的汇编代码如下:
0:003> !u 00a70070
Normal JIT generated code
....Main(System.String[])
Begin 00a70070, size 44
>>> 00a70070 55 push ebp
00a70071 mov ebp,esp
// 准备 GenericMethod
00a70073 mov ecx,3A30C4h (MD: ....GenericMethod[[System.String, mscorlib]]())
// 引用类型实际都共享一个 GenericMethod 方法的代码
00a70078 call dword ptr ds:[3A3098h] (....GenericMethod[[System.__Canon, mscorlib]](), ...)
// 调用 GenericMethod
00a7007e call dword ptr ds:[3A3108h] (....GenericMethod[[System.Int32, mscorlib]](), ...)
// 准备 GenericMethod
00a70084 mov ecx,3A3134h (MD: ....GenericMethod[[System.Object, mscorlib]]())
// 引用类型实际都共享一个 GenericMethod 方法的代码
00a70089 call dword ptr ds:[3A3098h] (....GenericMethod[[System.__Canon, mscorlib]](), ...)
// 调用 GenericMethod
00a7008f call dword ptr ds:[3A3178h] (....GenericMethod[[System.DateTime, mscorlib]](), ...)
// 准备 GenericMethod
00a70095 mov ecx,3A31A4h (MD: ....GenericMethod[[TestConsole.Program, TestConsole]]())
// 引用类型实际都共享一个 GenericMethod 方法的代码
00a7009a call dword ptr ds:[3A3098h] (....GenericMethod[[System.__Canon, mscorlib]](), ...)
// 调用 GenericMethod
00a700a0 call dword ptr ds:[3A31E8h] (....GenericMethod[[System.Double, mscorlib]](), ...)
*** WARNING: Unable to verify checksum for C:/.../mscorlib.ni.dll
// 调用 Console.ReadLine()
00a700a6 call mscorlib_ni+0x6d1c24 (71631c24) (System.Console.get_In(), mdToken: 06000771)
00a700ab mov ecx,eax
00a700ad mov eax,dword ptr [ecx]
00a700af call dword ptr [eax+64h]
00a700b2 pop ebp
00a700b3 ret
从这里我们可以看到, CLR 为引用类型( string/object/Program )生成共享的机器码,它们都实际上在调用一 个 GenericMethod 所生成的代码。而对于每个不同的值类型( int/DateTime /double ), CLR 则会为每种类型各生成一份。自然,您有充分的理由说:“调用的目标地址不一样,但是可能机器码是相同的”。此外, CLR 的“泛型 共享机器码”特性也并非如此简单,如果有多个泛型参数(且引用和值类型“混搭”)呢?如果虽然有泛型参数,但是确没有使用呢?关于这些,您可以自行进行验 证。本文的目的在于说明一些问题,并非是要把这一细节给深究到底。
总结
以上三个示例都是用IL 无法说明的,而这样的问题其实还有很多,例如:
引用类型和值类型是怎么分配的
GC是怎么分代,怎么工作的
Finalizer做什么的,对 GC 有什么影响
拆箱装箱到底做了些什么
CLR是怎么验证强签名程序集的
跨AppDomain 通信是怎么 Marshal by ref 或 by value 的
托管代码是怎么做P/Invoke 的
……
您会发现,这些东西虽然无法用IL 说明,却其中大部分可以说是最最基本的一些 .NET/CLR 工作方式的常识,更别说一些细节(数组存放方式,方法表结构)了。它们依旧需要别人来告诉您,您就算学会了 IL 指令,学会了 IL 表现逻辑的方式,您还是无法自己知道这些。
IL 还是太高级了,太高级了,太高级了…… CLR 作为承载 IL 的平台,负担的还是太多。与 CPU 相比, CLR 就像一个溺爱孩子的父母,操办了孩子生活所 需要的一切。这个孩子一嚷嚷“我要吃苹果”,则父母就会拿过来一个苹果。您咋看这个孩子,都还是无法了解父母是如何获得苹果的( new 一个 Apple 对 象),怎么为孩子收拾残局的( GC )。虽然这些经常是所谓的“成年人( .NET 程序员)必知必会”。而您如果盯着孩子看了半天,耐心分析他吃苹果的过程 (使用 IL 编写的逻辑),最后终于看懂了,可惜发现—— tmd 老子自己也会吃苹果啊(从 C# 等高级语言中也能看出端倪来)!不过这一点,还是由下一篇文章 来分析和论证吧。
这也是为什么各种 .NET 相关的书,即使是《 CLR via C# 》或《 Essential .NET 》此类偏重“内幕”的书,也只是告诉您什么是 IL ,它能做什么。然后大量的篇幅都是在使用各种示意图配合高级语言进行讲解,然后通过试验来进行验 证,不会盯着 IL 捉摸不停。同理,我们可以看到《 CLR via C# 》,《 CLR via VB.NET 》和《 CLR via CLI/C++ 》,但从来没有过《 CLR via IL 》。 IL 还是对应于高级语言,直接对应着 .NET 特性,而不是 CLR 的内部实现——既然 IL 无法说明比高级语言更多的东西,那么为什么要“ via IL ”?同样的例子还有, MSDN Magazine 的 CLR Inside Out 专栏也没有使用 IL 来讲解内容, Mono 甚至使用了与 MS CLR 不同实现方式来“编译”相同的 IL ( Mono 是不能参考任何 CLR 和 .NET 的代码的,一行都看不得)。你要了解 CLR ?那么多看看 Rotor ,多 看看 Mono ——看 IL 作用不大,它既不是您熟悉 CLR 的必要条件也不是充分条件,因为您关注的不是对 IL 的读取,甚至不是 IL 到机器码的转换方式,而是 CLR 各处所使用的方案。
最后,本文全篇在使用 WinDbg 进行探索,这并非要以了解 IL 作为基础,您完全可以不去关心 IL 那些缤纷复杂的指令的作用是什么。甚至于您完全忽略 IL 的存在,极端地“认为”是 C# 直接编译出的机器码,也不妨碍您来使用本文的做法来一探究竟——细节上会有不同,但是看到的东西是一样的。
不过这并不意味着,您不需要了解一些额外的东西。您需要具备哪些条件呢?
学习计算机组成原理,计算机体系结构等基础课程的内容,至少是这些课程中的基础。
以事实为基准,而不是“认为是,应该是”的办事方式。
严谨的态度,缜密的逻辑,大胆的推测。
……
“大胆的推测”和“认为是,应该是”并非一个意思。大胆的推测是根据已知现象,运用逻辑进行判断,从而前进,而最终这些推测要通过事实进行确定。正所谓“大胆推测,小心求证”。
以上这些是您“自行进行探索”所需要的条件,而如果您只是要“看懂”某个探索过程的话,就要看“描述”者的表达情况了。一般来说,看懂一个探索过程的要 求会低很多,相信只要您有耐心,并且有一些基本概念(与这些条件有关,与 IL 无关),想要看懂如上的探索过程,以及吸收最后的结论应该不是一件困难的事 情。
三、IL 可以看到的东西,其实大都也可以用 C# 来发现
我 们使用工具 .NET Reflector 来完成这部分知识的学习,从 .NET 1.x 开始, .NET Reflector 就是一个探究 .NET 框架(主要是 BCL )内部实现的有力工具,它可以把一个程序集高度还原成 C# 等高级语言的代码。在它的帮助下,几 乎所有程序集实现都变得一目了然,这大大方便了我们的工作。在某段不算短的时间内,使用 .NET Reflector 阅读过的代码数量远远超过了自己编写的代码。与此相反的是,几乎没有使用 IL 探索过 .NET 框架下的任何问题。这可能还涉及到方式方法 和个人做事方式,但是如果这真有效果的话,为什么要舍近求远呢?希望您看过了这篇文章,也可以像我一样摆脱 IL ,投入 .NET Reflector 的怀抱。
示例一:探究语言细节
C# 语言从 1.0 到 3.0 版本的进化过程中,大部分新特性都是依靠编译器的魔法。就拿 C#3.0 的各种新特性来说, Lambda 表达式, LINQ ,自动属性等 等,完全都是基于 CLR 2.0 中已有的功能,再配合新的 C# 编译器而产生的各种神奇效果。有些朋友认为,掌握 IL 之后便把握了 .NET 的根本,以不变应万变,只要读懂 IL ,那么 这些新特性都不会对您形成困扰。这话说的并没有错,只是,“掌握 IL ”在这里只是一个“充分条件”而不是一个“必要条件”,我们完全可以使用 .NET Reflector 将程序集反编译成 C# 代码来观察这些。
这里我们使用.NET Reflector 来观察最最常见,最最普通的 foreach 关键字的功能。我们都知道 foreach 是遍历一个 IEnumerble 对象内元素的方式, 我们也都知道 foreach 其实是 GoF Iterator 模式的实现,通过 MoveNext 方法和 Current 属性进行配合共同完成。不过大部分朋友似乎都是从 IL 进行观察,或是“听别人 说” 而了解这些的。事实上, .NET Reflector 也可以很容易地证实这一点,只是这中间还有些“特别”的地方。那么首先,我们还是来准备一个最简单的 foreach 语句:
static void DoEnumerable(IEnumerable source)
{
foreach (int i in source)
{
Console.WriteLine(i);
}
}
如果观察它的IL 代码,即使不了解 IL 的朋友也一定可以看出,其中涉及到了 GetEnumerator , MoveNext 和 Current 等成员的访问:
.method private hidebysig static void DoEnumerable(
class [mscorlib]System.Collections.Generic.IEnumerable`1 source) cil managed
{
.maxstack 1
.locals init (
[0] int32 i,
[1] class [mscorlib]System.Collections.Generic.IEnumerator`1 CS$5$0000)
L_0000: ldarg.0
L_0001: callvirt instance class [mscorlib]System.Collections.Generic.IEnumerator`1
[mscorlib]System.Collections.Generic.IEnumerable`1::GetEnumerator()
L_0006: stloc.1
L_0007: br.s L_0016
L_0009: ldloc.1
L_000a: callvirt instance !0 [mscorlib]System.Collections.Generic.IEnumerator`1::get_Current()
L_000f: stloc.0
L_0010: ldloc.0
L_0011: call void [mscorlib]System.Console::WriteLine(int32)
L_0016: ldloc.1
L_0017: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
L_001c: brtrue.s L_0009
L_001e: leave.s L_002a
L_0020: ldloc.1
L_0021: brfalse.s L_0029
L_0023: ldloc.1
L_0024: callvirt instance void [mscorlib]System.IDisposable::Dispose()
L_0029: endfinally
L_002a: ret
.try L_0007 to L_0020 finally handler L_0020 to L_002a
}
但是,如果使用.NET Reflector 观察它的 C# 代码又会如何呢?
private static void DoEnumerable(IEnumerable source)
{
foreach (int i in source)
{
Console.WriteLine(i);
}
}
请 注意,以上这段是由 .NET Reflector 从 IL 反编译后得到的 C# 代码,这简直……不是简直,是完完全全真真正正地和我们刚才写的代码一模一样!这就是 .NET Reflector 的强大之处,由于它意识到 IL 调用了 IEnumerable.GetEnumerator 方法,因此它就“留心”判 断这个调用的“模式”是否符合一个标准的 foreach ,如果是,那么就会显示为一个 foreach 语句而不是 while...MoveNext 。不过, 这难道不就掩盖了“事物本质”了吗?要知道我们的目标可是探究 foreach 的形式,既然 .NET Reflector 帮不了的话,我们不还是需要去观察 IL 吗?
刚才提到,.NET Reflector 在判断 IL 代码时发现一些标准的模式时会进行代码“优化”。那么我们能否让 .NET Reflector 不要做这种“优化”呢?答案是肯定的,只是需要您在 .NET Reflector 中进行一些简单的设置:
打 开 View 菜单中的 Options 对话框,在左侧 Disassembler 选项卡中修改 Optimization 级别,默认很可能是 .NET 3.5 ,而现在我们要将其修改为 None 。这么做会让 .NET Reflector 最大程度地“直接”翻译 IL 代码,而不做一些额外优化。将 Optimization 级别设为 None 以后, DoEnumerable 方 法的代码就变为了:
static void DoEnumerable(IEnumerable source)
{
int num;
IEnumerator enumerator;
enumerator = source.GetEnumerator();
Label_0007:
try
{
goto Label_0016;
Label_0009:
num = enumerator.Current;
Console.WriteLine(num);
Label_0016:
if (enumerator.MoveNext() != null)
{
goto Label_0009;
}
goto Label_002A;
}
finally
{
Label_0020:
if (enumerator == null)
{
goto Label_0029;
}
enumerator.Dispose();
Label_0029: ;
}
Label_002A:
return;
}
这 是 C# 代码吗?为什么会有那么多的 goto ?为什么 MoveNext 方法返回的布尔值可以和 null 进行比较?其实您把这段代码复制粘贴后会发现,它能够 正常编译通过,效果也和刚才的 foreach 语句完全一样。这就是去除“优化”的效果。在上一篇文章中谈到说: IL 和 C# 一样,都是用于表现程序逻辑。 C# 使用 if...else 、 while 、 for 等等丰富语法,而在 IL 中就会变成判断 + 跳转语句。上面的 C# 代码便直接保留了 IL 的这个 “特性”。不 过还好,我们还是可以看出 try...finally ,可以看出 MoveNext 方法和 Current 属性的访问,可以看到程序使 用 Console.WriteLine 输出数据。至此,我们便发现了 foreach 语句的真面目。从现在开始,在您准备深入 IL 之前,建议您可以尝试一 下使用 None Optimization 来观察 C# 代码。
实事求是地说,上面的 C# 代码的“转向逻辑”并不那么清晰,因此您在理解的时候可以把它复制到编辑器中,进行一些简单调整。但是从我的经验上来看,需要 使用 None Optimization 进行探索的地方非常少见。 foreach 是一个,还有便是 C# 中的其他一些“别名”,如使用 using 关键字管 理 IDisposable 对象,以及 lock 关键字。而且,其实这段逻辑也只是没有优化 IL 中的跳转语句而已,已经比 IL 本身要直观许多了。此外,关于 对象创建,变量声明,方法调用,属性访问,事件加载……一切的一切都还是最常用的 C# 代码。因为还是那个原因:从大部分情况上来看, IL 也只是表现了程序 逻辑,并没有比 C# 等语言体现出更多的细节。
我在这里举了一个较为极端的例子,因为我发现不少朋友并没有尝试过使用 None Optimization 来观察过代码。这里也可以看出, .NET Reflector 的“优化级别”还不够“细致”。不过这应该是一个“产品设计”的正常结果,因为 foreach/using/lock 的关键字都是 从 .NET 1.0 诞生伊始就存在的,也就是说,即使 .NET Reflector 选择将 IL 编译为 C# 1.0 ,它的表现形式依旧是“标准模式”,这方面可能就不能过于强求了吧。至于其他一些探索,例如 C# 中的自动属性, Lambda 表达式构建表达式树或匿 名委托,乃至 C# 4.0 中的 dynamic 关键字,都是使用 .NET 3.5 Optimization 进行探索便可得知的结果。您可以回忆一下自己看过的文章,其中有多少是使用 IL 解释问题的呢?
示例二:学习.NET 平台上的其他语言
在 .NET 平台上,任何语言都会先编译为 IL ,然后再运行时由 JIT 转化为机器码。因此有种说法是,只要把握了 IL , .NET 平台上各种语言之间的迁移 都会变得容易。对此不同看法。在以前讨论语言是否重要的时候,提到,语言它并不仅仅是一种文字表现形式,而是一种“思维方式”的改变,这可能会影响到您程 序的编码风格, API 设计乃至架构(这个链接可能打不开,因为……)。实际上,如果您只是在 C# 与 VB.NET 之间进行迁移,原本就是一件相当容易的事 情,因为它们之间“语言”的各种概念和特性都非常接近。而一种改变您思维的语言,才是真正有价值,而且值得进行比较和探索的。如果一味地追求“把握本 源”,那么甚至还有比 IL 更低抽象的事务,但这些就已经违背了“创造一门语言”,以及您学习它的目的了,不是吗?
当然,探索也是需要的,尤其是 .NET 平台上的各种语言,他们被统一在同样的平台上,这本身就是一种很好的资源。这种资源就是所谓的“比较学习”。您可 以把新的语言和您熟悉的语言进行对比,吸收其中的长处(如优秀的思维方式),这样便可以更好地使用旧有语言。例如,您把 F# 类库转化为 C# 代码进行观察之 后,发现其中大量函数式编程风格的 API 是使用“委托”来实现的,您可能就会想到是否可以设计出函数式编程风格的 C# API ,是否可以把 F# 中 List 或 Seq 模块中的各种高阶函数移植到您自己的项目中来。这就有了更好的价值,这价值也不仅仅只是您“学会了新的语言”。
例如,我们现在使用尾递归来计算斐波那契数列。在之前的文章中,我们的作法是:
private static int FibTail(int n, int acc1, int acc2)
{
if (n == 0) return acc1;
return FibTail(n - 1, acc2, acc1 + acc2);
}
public static int Fib(int n)
{
return FibTail(n, 0, 1);
}
为 了“尾递归”,我们必须定义一个私有的 FibTail 方法,接收三个参数。而对外的接口还是一个公有的 Fib 方法,它返回斐波那契数列第 n 项的结果。这个 示例很简单,作法也没有任何问题。但是我有时候会觉得,我们为什么非要定义一个额外的“辅助方法”,然后在现有的方法里只是进行一个简单的转发?如果这个 辅助方法会在其他地方得到调用也就罢了(我们遵守了 DRY 原则),但是现在却有点“平白无故”地在代码里增加了一个方法,这样在 VS 的 Class View 或编辑器上方的下拉列表中也会多出一项。此外,为了表示两个方法的关系,您可能还会使用 region 把它们包裹起来……
不过在F# 中,上面的尾递归就可以这样写:
let fib n =
let rec fibTail x acc1 acc2 =
match x with
| 0 -> acc1;
| _ -> fibTail (x - 1) acc2 (acc1 + acc2)
fibTail n 0 1
在 fib 方法内部,我们可以重新定义一个 fibTail 方法,其中实现了尾递归。对于外部来说,只有 fib 方法是公开的,外界丝毫不知 道 fibTail 方法的存在,这种定义内部函数的作法在 F# 中非常常见。而编译后,我们在 .NET Reflector 中便可看到与之对应的 C# 实现:
public static int fib(int n)
{
switch (n)
{
case 0:
return 0;
}
return fibTail@7@7(n - 1, 1, 1);
}
internal static int fibTail@7@7(int x, int acc1, int acc2)
{
...
}
在 F# 中没有 internal 的访问级别,您可以认为这里 internal 便是 private 。于是我们得知(可能您本身也猜得到):由于 .NET 本身 并没有“嵌套方法”特性,因此在这里编译器会重新生成一个特殊的私有方法,并且在 fib 方法里进行调用。于是我们想到,这个“自动生成方法”的特性,在 C# 中也有体现啊。例如, IEnmuerable 有一个扩展方法是 Where ,我们可以用 Lambda 表达式构造一个匿名委托作为参 数……唔唔,这不就相当于把一个方法定义在另一个方法内部了吗?于是,我们修改一下之前 C# 的尾递归的实现:
public static int Fib(int n)
{
Func fibTail = null;
fibTail = (x, acc1, acc2) =>
{
if (x == 0) return acc1;
return fibTail(x - 1, acc2, acc1 + acc2);
};
return fibTail(n, 0, 1);
}
如果没有 F# 的“提示”,可能我们只能想到 list.Where(i => i % 2 == 0) 这种形式的用法,我们平时不会在方法内部额外地“创建一个委托”,然后加以调用,而且还用到了“递归”——甚至还是“尾递归”(虽然 C# 编译器在这里 没有进行优化,而且这里其实也只是个“伪递归”,因为 fibTail 其实是个可改变的“函数指针”)。不过,由于我们刚才通过 C# 来观察 F# 的编译结果, 联想到它和我们以前观察到的 C# 中“某个特性”非常相似,再加上合理的尝试,最终同样得出了一个还算“令人满意”的使用方式。
这只是一个示例,我并不是说这种作法是所谓的“最佳实践”。任何办法一旦遭到滥用也肯定不会有好处,您要根据当前情况判断是否应该采取某种作法。刚才的 演示只是为了说明,我们应该如何从其他语言中吸取优势思想,改进我们的编程工作。当然,您使用 IL 来探索新的语言也没有太大问题, C# 能看到的东西用 IL 也可以看到。但是请您回想一下,即使您平时学习 IL ,您想过直接使用 IL 来写程序吗?您学习和探索新语言的目的,只是为了搞清楚它的 IL 表现形式吗?为什 么您不使用简单易懂的 C# ,却要纠缠于 IL 中那些纷繁复杂的指令呢?
示例三:性能相关
学习IL 对写出高性能的 .NET 程序有帮助吗?
记得以前在学习“计算机系统概论”课程时,有一个实验就是为几段 C 程序进行优化。当时的手段可谓无所不用其极,例如内联一个子过程以避免 call 指令 的消耗,或把一段 C 代码使用汇编进行替换等等。从结果上看,它们都能对性能有“明显”的提高。不过,那些都是为了加深概念而进行的练习,并不是说在现代程 序中应该使用这种方式进行优化。现在早已不是在“指令级别”进行性能优化的时期了,连操作系统内核也只是在一些对性能要求非常高的地方,如内存管理,线程 调度中的细微方面使用汇编来编写,其余部分也都是用 C 语言来完成。这并不是仅仅是因为“可维护性”等考虑,也有部分原因是因为在目前编译技术的发展下,一 些极端的做法已经很难产生有效的优化效果了(例如一般来说来,程序员写出的 C 代码的性能会优于他写的汇编代码)。
此外,在您不知道 JIT 究竟作了什么事情的情况下,观察 IL 这样一种高度抽象的语言,您还是无法真正判断出一个程序从微观上的性能如何。不过这并不是 说,现代程序不应该“主动”追究性能,而是说,现代程序在性能优化问题上并非如此简单,它涉及到的东西会更多,需要更加合适的手段。例如,即使您内联了一 个子过程,也只是减少了 call 指令的所带来的消耗,但是这与这个子过程本身“一长串”指令相比,所带来的提高是微乎其微的。而如果您一旦破坏了 Locality 或造成了 False Sharing ,或造成了资源竞争等等,这可能就会造成数倍甚至更多的性能损耗。换句话说,影响现代应用程序的性能的因素大都是“宏观”的,用通俗的话来 说,一般都是“写法”上造成的问题。
这也是为什么说“ Make clean code fast ”远比“ Make fast code clean ”来的容易,现代程序更注重的是“清晰”而并非是“性能”。因为程序清晰,更容易让人发现性能瓶颈究竟在何处,可以进行有针对性地优化(即使是 那种在极端性能要求下故意进行的“丑陋”写法,也是为了高性能而“丑陋”,而不是因为“丑陋”而高性能,分清这一点很重要)。换句话说,如果我们有一种更 清晰地方式来查看同样的程序实现,不也降低了探索程序性能瓶颈的难度吗?那么,同样一段程序,您会通过 C# 进行观察,还是使用 IL 呢?
有朋友可能会说:即使无法把握 JIT 对于 IL 的优化,但是从 IL 中可以看出高级语言,如 C# 的编译器的优化效果啊。这话本没有错,但问题还是在于, C# 的编译器优化效果,是否在“反编译”回来之后就无法观察到了呢?“优化过程”往往都是不可逆的,它会造成信息丢失,导致我们很难从“优化结果”中看出“原 始模样”,这一点在上一篇文章中也有过论述。换句话说,我们通过 C# => IL => C# 这一系列“转化”之后,几乎都可以清楚地发现 C# 编译器做过哪些优化。这里还是使用经典的 foreach 作为示例,您知道以下两个方法的性能高低如 何?
static void DoArray(int[] source)
{
foreach (int i in source)
{
Console.WriteLine(i);
}
}
static void DoEnumerable(IEnumerable source)
{
foreach (int i in source)
{
Console.WriteLine(i);
}
}
经过了C# 编译器的优化,再使用 .NET Reflector 查看 IL 反编译成 C# ( None Optimization )的结果,就会发现它们变成了此般模样:
private static void DoArray(int[] source)
{
int num;
int[] numArray;
int num2;
numArray = source;
num2 = 0;
goto Label_0014;
Label_0006:
num = numArray[num2];
Console.WriteLine(num);
num2 += 1;
Label_0014:
if (num2 < ((int)numArray.Length))
{
goto Label_0006;
}
return;
}
private static void DoEnumerable(IEnumerable source)
{
int num;
IEnumerator enumerator;
enumerator = source.GetEnumerator();
Label_0007:
try
{
goto Label_0016;
Label_0009:
num = enumerator.Current;
Console.WriteLine(num);
Label_0016:
if (enumerator.MoveNext() != null)
{
goto Label_0009;
}
goto Label_002A;
}
finally
{
Label_0020:
if (enumerator == null)
{
goto Label_0029;
}
enumerator.Dispose();
Label_0029: ;
}
Label_002A:
return;
}
C# 编译器的优化效果表露无遗:对于 int 数组的 foreach 其实是被转化为类似于 for 的下标访问遍历,而对 于 IEnumerable 还是保持了 foreach 关键字中标准的“ while...MoveNext ”模式(如果您 把 Console.WriteLine 语句去掉的话,就可以使用 .NET 3.5 Optimization 直接看出两者的不同,您不妨一试)。由此看来, DoArray 的性能会比后两者要高。事实上,由于性能主要是由“实现方式”决定 的,因此我们可以通过反编译成 C# 代码的方式来阅读 .NET 框架中的大部分代码, IL 在这里起到的效果很小。例如在文章《泛型真的会降低性能吗?》 里, Teddy 大牛就通过阅读 .NET 代码来发现数组的 IEnumerable 实现,为什么性能远低于 IEnumerable 。
不过,判断两者性能高低,最简单,也最直接的方式还是进行性能测试。例如您可以使用CodeTimer 来比较 DoArray 和 DoEnumerable 方法的性能,一目了然。
值得一提的是,如果要进行性能优化,需要做的事情有很多,而“阅读代码”在其中的重要性其实并不高,而且它也最容易误入歧途的一种。“阅读代码”充其量 是一种人工的“静态分析”,而程序的运行效果是“动态”的。这篇文章解释了为什么使用 foreach 对 ArrayList 进行遍历的性能会比 List 低,其中使用了 Profiler 来说明问题。 Profiler 能告诉我们很多难以观察到的事情,例如在遍历中究竟是 ArrayList 哪个方法消耗时间最长。此外它还发现了 ArrayList 在遍历时创建了大量的对象,这种对于内存资源的消耗,几乎不可能从一小段代码 中观察得出。此外,不同环境下,同样的代码可能执行效果会有不同。如果没有 Profiler ,我们可能会选择把一段执行了 100 遍的代码性能提升 1 秒 钟,却不会把一段执行 100000 遍的代码性能提升 100 毫秒。性能优化的关键是“有的放矢”,如果没有 Profiler 帮我们指明道路,做到这一点相当 困难。
其实对于性能方面说的这些,可以大致归纳为以下三点:
·关注IL ,对于从微观角度观察程序性能很难有太大帮助,因为您很难具体指出 JIT 对 IL 的编译方式。
·关注IL ,对于从宏观角度观察程序性能同样很难有太大帮助,因为它的表述能力不会比 C# 来的直观清晰。
·性能优化,最关键的一点是使用Profiler 来找出性能瓶颈,有的放矢。
所以,如果您问:“学习IL ,对写出高性能的 .NET 程序有帮助吗?”回答:“有,肯定有啊”。
但是,如果您问:“我想写出高性能的.NET 程序,应该学习 IL 吗?”回答:“别,别学 IL ”。
总结
feilng 在前文留下的一些评论,我认为说得非常有道理:
IL只是在 CLR 的抽象级别上说明干什么,而不是怎么干……重要的是要清楚在现实条件下,需要进入那个层次才能获取足够的信息,掌握接口的完整语义和潜在副作用。
IL 的确比 C# 等高级语言来的所谓“底层”,但是很明显, IL 本身也是一种高级抽象。而即使是机器码,它也可以说是基于 CPU 的抽象, CPU 上如流水 线,并行,内存模型, Cache Lock 等东西对于汇编 / 机器码来说也可以说是一种“封装”。从不同层次可以获得不同信息,我们追求“底层”的目的肯定也不是“底层”这两个字,而是一种 收获。了解自身需要什么,然后能够选择一个合理的层次进入,并得到更好的收益,这本身也是一种能力。追求 IL 的做法,本身并没有错,只是追求 IL 一定是当 前情况下的最优选择吗?这是一个值得不断讨论的问题,我的这篇文章也只是表达了我个人对某些问题的看法。
1、如何看到元件的中间语言吗?
Microsoft 提供了一个称为 Ildasm 的工具,它可以用来查看元件的 metadata 和 IL 。
2、能否通过反向工程从 IL 中获得源代码?
是的。相对而言,从 IL 来重新生成高级语言源代码 ( 例如 C#) 通常是很简单的。
3、如何防止别人通过反向工程获得我的代码?
目前唯一的办法是运行带有 /owner 选项的 ilasm 。这样生成的元件的 IL 不能通过 ildasm 来查看。然而,意志坚定的代码破译者能够破解 ildasm 或者编写自己的 ildasm 版本,所以这种方法只能吓唬那些业余的破译者。
不幸的事,目前的 .NET 编译器没有 /owner 选项,所以要想保护你的 C# 或 VB.NET 元件,你需要像下面那样做:
csc helloworld.cs
ildasm /out=temp.il helloworld.exe
ilasm /owner temp.il
(这个建议是 Hany Ramadan 贴到 DOTNET 上的。 )
看起来过一段时间能有 IL 加密工具 ( 无论来自 Microsoft 或第三方 ) 。这些工具会以这样的方式来“优化” IL :使反向工程变得更困难。
当然,如果你是在编写 Web 服务,反向工程看起来就不再是一个问题,因为客户不能访问你的 IL 。
4、我能直接用 IL 编程吗?
是的。Peter Drayton 在 DOTNET 邮件列表里贴出了这个简单的例子:
程序代码
.assembly MyAssembly {}
.class MyApp {
.method static void Main() {
.entrypoint
ldstr "Hello, IL!"
call void System.Console::WriteLine(class System.Object)
ret
}
}
将其放入名为 hello.il 的文件中,然后运行 ilasm hello.il ,将产生一个 exe 元件。
5、 IL 能做到 C# 中做不到的事吗?
是的。一些简单的例子是:你能抛出不是从 SystemException 导出的异常,另外你能使用非以零起始的数组。
你可能感兴趣的:(.net)
.NET版Excel处理控件Aspose.Cells v20.2新增支持CSV到JSON的转换
Lee-Shyllen
Aspose 文档管理 文档格式转换 aspose 文件格式处理 excel 文档开发
Aspose.Cellsfor.NET是Excel电子表格编程API,可加快电子表格管理和处理任务,同时支持构建具有生成,修改,转换,呈现和打印电子表格功能的跨平台应用程序。近日,.NET版Aspose.Cellsfor.NET迎来了2020年2月更新,支持Pipe-delimited/CSV到JSON的转换和数据透视表与外部连接之间的链接,改善PivotTable.CalculateStyle的
FastExcel使用教程
束恺俭Jessie
FastExcel使用教程FastExcelFastExcelReadingandWritingin.Net项目地址:https://gitcode.com/gh_mirrors/fa/FastExcel项目介绍FastExcel是一款专为.Net开发环境设计的高性能Excel读写库,它提供了一种快速且内存占用小的方式来处理.xlsx文件。不同于依赖OpenXMLSDK的方式,FastExcel直
FastExcel 使用指南
嵇殉嵘Eliza
FastExcel使用指南FastExcelFastExcelReadingandWritingin.Net项目地址:https://gitcode.com/gh_mirrors/fa/FastExcelFastExcel是一个用于.NET平台的库,它提供了快速读取和写入Excel文件的能力,特别适用于处理大量数据时保持较低的内存占用。本指南将帮助您了解项目的基本结构、启动与配置细节。1.项目目录
Spring Boot + Spring Security + OAuth2 搭建 (一)
hello_world!
框架工具 springboot spring
https://blog.csdn.net/wllovar/article/details/87621910源代码地址:https://download.csdn.net/download/wllovar/10963011具体那里有疑问可以给我发邮件1044560183@qq.com最近应项目需求想搞一些类似微信公众平台那样的提供第三方访问的API,这就要用到OAuth2,具体OAuth2的认证流
【实用技能】如何借助Excel处理控件Aspose.Cells,使用 C# 锁定 Excel 中的单元格
CodeCraft Studio
文档管理 控件 excel c# 开发语言
锁定Excel中的单元格对于数据完整性至关重要。它可以防止用户更改重要信息。此功能广泛用于财务、项目管理和数据分析。通过锁定单元格,您可以确保关键数据保持不变。这可以增强协作并减少错误。在这篇博文中,我们将探讨如何使用C#锁定Excel中的单元格。C#Excel库用于锁定或解锁单元格Aspose.Cellsfor.NET是一个功能强大的Excel文件处理库。它简化了Excel中锁定单元格等任务。使
安卓通过网络获取位置的方法
爱学习的大牛123
开发语言 android 网络定位
一方法介绍1.基本权限设置首先需要在AndroidManifest.xml中添加必要权限:```xml```2.使用NetworkLocationProvider```javaLocationManagerlocationManager=(LocationManager)getSystemService(Context.LOCATION_SERVICE);//检查是否启用了网络定位booleani
Kubernetes监控,查看日志
稚辉君.MCA_P8_Java
Kubernetes Cluster kubernetes 云原生 运维 容器
Kubernetes监控与日志1、查看集群资源状态在Kubernetes集群中,查看集群资源状态和组件状态是非常重要的操作。以下是一些常用的命令和解释,帮助你更好地管理和监控Kubernetes集群。1.1查看master组件状态Kubernetes的Master组件包括APIServer、ControllerManager和Scheduler。可以使用kubectlgetcs(cs:compon
进化式架构实例指南
裴辰垚Simone
进化式架构实例指南evolutionary-architecture-by-exampleNavigatethecomplexlandscapeof.NETsoftwarearchitecturewithourstep-by-step,story-likeguide.Unpacktheinterplaybetweenmodularmonoliths,microservices,domain-dri
WebForms DataList 深入解析
wjs2024
开发语言
WebFormsDataList深入解析引言在Web开发领域,控件是构建用户界面(UI)的核心组件。ASP.NETWebForms框架提供了丰富的控件,其中DataList控件是一个灵活且强大的数据绑定控件。本文将深入探讨WebFormsDataList控件的功能、用法以及在实际开发中的应用。DataList控件概述1.1什么是DataList控件DataList控件是ASP.NETWebForm
Camera to NDI 局域网视频传输的高效之选
weixin_41951035
信号处理 windows 视频编解码 实时音视频 webrtc
探索CameratoNDI:局域网视频传输的高效之选前言`在视频技术不断革新的当下,对于低延迟、高质量的局域网视频传输需求日益增长。CameratoNDI这款以NDI协议为核心的视频传输软件应运而生,为众多用户提供了出色的解决方案,而我(爱折腾的木匠)有幸参与了它部分代码的开发。NDI(NetworkDeviceInterface)协议是一种专门用于网络视频传输的标准,它能够在局域网络环境中实现极
Unet 改进:在encoder和decoder间加入TransformerBlock
听风吹等浪起
AI 改进系列 transformer 图像分割 Unet
目录1.TransformerBlock2.Unet改进3.完整代码Tips:融入模块后的网络经过测试,可以直接使用,设置好输入和输出的图片维度即可1.TransformerBlockTransformerBlock是Transformer模型架构的基本组件,广泛应用于机器翻译、文本摘要和情感分析等自然语言处理任务。TransformerBlock是一个由两个子组件组成的构建块:多头注意力机制和前
python读取nc文件并转换成csv_使用Python截取nc文件数据保存到CSV文件-Go语言中文社区...
达拉崩吧叭叭叭
问题要求:编写一个函数完成以下任务:截取经度在23°N-40°N,纬度在118°E-131°E范围内各属性不同深度的数据,使用Python中合适的数据结构将截取的数据保存到同名CSV文件中。(nc文件数据格式参见笔者其他文章)实验内容(附代码)实验数据介绍(通过实验介绍你对NC数据的认识)nc文件即NetCDF全称为networkCommonDataFormat,中文译法为“网络通用数据格式”。一
反激变换器的详细设计步骤,附详细计算公式
洛溪之恋
电源设计 开源 单片机
BMS开发板BMS开发板:https://blog.csdn.net/u010474219/article/details/135313436?spm=1001.2014.3001.5502齐纳管吸收漏感能量的反激变换器:0.设计前需要确定的参数A开关管Q的耐压值:VmqB输入电压范围:Vinmin~VinmaxC输出电压VoD电源额定输出功率:Po(或负载电流Io)E电源效率:XF电流/磁通密度
Java锁自定义实现到aqs的理解
master-dragon
# Java并发编程 java 开发语言
专栏系列文章地址:https://blog.csdn.net/qq_26437925/article/details/145290162本文目标:理解锁,能自定义实现锁通过自定义锁的实现复习Thread和Object的相关方法开始尝试理解Aqs,这样后续基于Aqs的的各种实现将能更好的理解目录锁的自定义实现lock自旋加锁改进1:自旋加锁失败的尝试让出cpu(yield操作)改进2:yield换成
深入详解高性能消息队列中间件 RabbitMQ
dvlinker
C/C++实战专栏 C/C++软件开发从入门到实战 中间件 rabbitmq 分布式 消息队列中间件
目录1、引言2、什么是RabbitMQ?3、RabbitMQ优势4、RabbitMQ整体架构剖析4.1、发送消息流程4.2、消费消息流程5、RabbitMQ应用5.1、广播5.2、RPCVC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/124272585
如何进行MAC地址追踪
ManageEngine卓豪
IP 地址管理 MAC地址 IP地址追踪 IP地址管理
MAC地址(MediaAccessControlAddress),直译为媒体存取控制位址,也称为局域网地址(LANAddress),MAC位址,以太网地址(EthernetAddress),硬件地址(HardwareAddress)或物理地址(PhysicalAddress),用来确认网络设备位置的位址。每个设备都分配有一个唯一的硬件ID,这些都包含在每个设备的网络接口控制器(NIC)中,此编号用
linux telnet rpm 安装包,centos7系统之telnet命令rpm包安装
clowntom
linux telnet rpm 安装包
#centos7系统之telnet命令rpm包安装[root@ywb~]#cdrpm/[root@ywbrpm]#ll总用量224-rw-r--r--1rootroot5904010月1713:24telnet-0.17-47.el6.x86_64.rpm-rw-r--r--1rootroot3736010月1713:24telnet-server-0.17-47.el6.x86_64.rpm-r
LightM-UNet(2024 CVPR)
刘若里
论文阅读 网络 学习 笔记 计算机视觉 人工智能
论文标题LightM-UNet:MambaAssistsinLightweightUNetforMedicalImageSegmentation论文作者WeibinLiao,YinghaoZhu,XinyuanWang,ChengweiPan,YashaWangandLiantaoMa发表日期2024年01月01日GB引用>WeibinLiao,YinghaoZhu,XinyuanWang,eta
人脸识别国内镜像
未来之窗软件服务
android
Downloadhttps://maven.aliyun.com/repository/central/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.9.20/kotlin-stdlib-jdk8-1.9.20.pom,took290msDownloadhttps://maven.aliyun.com/repository/central/net/sf/kxm
安卓安全访问配置说明network-security-config —未来之窗跨平台操作
未来之窗软件服务
android 安全 java
一、放行特定的IP地址和端口您要放行的特定IP地址您要放行的端口号二、放行IP段的示例配置192.168.0.0/24true三、放行IP段最后两部分的示例配置 192.168.*.* true
Linux中 端口被占用如何解决
烛照103
Linux专栏相关 开发工具 开发中的错误解决 linux 服务器 网络
lsof命令查找查找被占用端口lsof-i:端口号#示例lsof-i:8080lsof-i:3306netstat命令查找查找被占用端口netstat-tuln|grep端口号#示例netstat-tuln|grep3306netstat-tuln|grep6379ss命令查找查找被占用端口ss-tunlp|grep端口号#示例ss-tunlp|grep3306终止端口进程kill-9PID#示例
【转摘】域名服务器配置学习笔记
weixin_30725467
数据库 嵌入式 运维
括看相关的rfc文件,一看和dns相关的rfc文件,妈呀,居然有86个之多。能看多少是多少吧。先把DNS的原理研究透彻了。在看rfc文件我想会事半功倍的:)1.ICANN是干什么的?和他的一些相关资讯?ICANN全称是叫:InternetCorporationforAssignedNamesandNumbers(互联网名称与数字地址分配机构),是一个非盈利性的国际组织,负责互联网协议(IP)地址的
【课程设计推荐】基于JSP的书店系统设计与实现
想念@思恋
课程设计 jsp java 课程设计 java jsp
关注【墨岚创客】,回复【毕设】,赠送免费毕设资源,具体联系方式见文末引言二十一世纪是一个集数字化,网络化,信息化的,以网络为核心的社会。当钱天白教授于1986年9月14日在北京计算机应用技术研究所内向德国卡尔斯鲁厄大学发出第一封电子邮件“穿越长城,走向世界”的时候,他也许不知道自己推开了中国信息时代的大门;1994年4月20日,中国科学院计算机中心通过美国Sprint公司连入Internet的64
13.3:.NET的容器化和容器编排工具的使用(课程共5750字,4段代码举例)
小兔子平安
.NET完整学习全解答 python
①在本示例中,xn--Docker-gn7igl13d91b569eha512r584d.NETCore应用程序容器化。②在本示例中,我们将使用Kubernetes来进行容器编排。③在本示例中,我们将使用DockerCompose进行多容器编排。④在本示例中,我们将使用Azure容器实例来托管容器。以下是一个示例docker-compose.yml文件:version:'3'services:we
Azure Cosmos DB Repository .NET SDK:简化您的数据操作
费然杨Bernadette
AzureCosmosDBRepository.NETSDK:简化您的数据操作azure-cosmos-dotnet-repositoryWrapsthe.NETSDKforAzureCosmosDBabstractingawaythecomplexity,exposingasimpleCRUD-basedrepositorypattern项目地址:https://gitcode.com/gh_m
探索全球分布式数据库的新篇章:Azure Cosmos DB .NET SDK v3
施刚爽
探索全球分布式数据库的新篇章:AzureCosmosDB.NETSDKv3azure-cosmos-dotnet-v3.NETSDKforAzureCosmosDBforthecoreSQLAPI项目地址:https://gitcode.com/gh_mirrors/az/azure-cosmos-dotnet-v3AzureCosmosDB.NETSDKv3是一个强大的开发工具包,专为连接并利用
Clean Architecture with Azure Cosmos DB 项目教程
吕奕昶
CleanArchitecturewithAzureCosmosDB项目教程clean-architecture-azure-cosmos-dbAstartingpointtobuildawebAPItoworkwithAzureCosmosDBusing.NET5andAzureCosmosDB.NETSDKV3,basedonCleanArchitectureandrepositorydesi
探索Azure Cosmos DB的清洁架构:高效构建现代Web API
仰书唯Elise
探索AzureCosmosDB的清洁架构:高效构建现代WebAPIclean-architecture-azure-cosmos-dbAstartingpointtobuildawebAPItoworkwithAzureCosmosDBusing.NET5andAzureCosmosDB.NETSDKV3,basedonCleanArchitectureandrepositorydesignpat
Kubernetes 中 LimitRange 与 ResourceQuota 的深度剖析
大大宝的博客
k8s kubernetes 贪心算法 容器
摘要:Kubernetes(简称k8s)作为容器编排领域的事实标准,提供了丰富的资源管理机制来确保集群的高效、稳定运行。LimitRange和ResourceQuota是其中两个重要的资源管理工具,它们在不同层面发挥着关键作用。本文深入探讨了LimitRange和ResourceQuota的用途、工作原理,并详细分析了它们之间的差异,旨在帮助读者全面理解和有效运用这两个工具,提升Kubernete
Gateway API:Kubernetes中的动态基础设施配置与流量路由
大大宝的博客
k8s gateway kubernetes 容器
摘要:本文详细介绍了Kubernetes中的GatewayAPI,包括其设计原则、资源模型、请求流程、一致性等方面。GatewayAPI提供了动态基础设施配置和高级流量路由功能,通过可扩展、面向角色和协议感知的配置机制使网络服务可用。一、引言GatewayAPI是一系列API类型,用于提供动态基础设施配置和高级流量路由功能。它利用可扩展、面向角色且具有协议感知的配置机制,使网络服务得以应用。Gat
iOS http封装
374016526
ios 服务器交互 http 网络请求
程序开发避免不了与服务器的交互,这里打包了一个自己写的http交互库。希望可以帮到大家。
内置一个basehttp,当我们创建自己的service可以继承实现。
KuroAppBaseHttp *baseHttp = [[KuroAppBaseHttp alloc] init];
[baseHttp setDelegate:self];
[baseHttp
lolcat :一个在 Linux 终端中输出彩虹特效的命令行工具
brotherlamp
linux linux教程 linux视频 linux自学 linux资料
那些相信 Linux 命令行是单调无聊且没有任何乐趣的人们,你们错了,这里有一些有关 Linux 的文章,它们展示着 Linux 是如何的有趣和“淘气” 。
在本文中,我将讨论一个名为“lolcat”的小工具 – 它可以在终端中生成彩虹般的颜色。
何为 lolcat ?
Lolcat 是一个针对 Linux,BSD 和 OSX 平台的工具,它类似于 cat 命令,并为 cat
MongoDB索引管理(1)——[九]
eksliang
mongodb MongoDB管理索引
转载请出自出处:http://eksliang.iteye.com/blog/2178427 一、概述
数据库的索引与书籍的索引类似,有了索引就不需要翻转整本书。数据库的索引跟这个原理一样,首先在索引中找,在索引中找到条目以后,就可以直接跳转到目标文档的位置,从而使查询速度提高几个数据量级。
不使用索引的查询称
Informatica参数及变量
18289753290
Informatica 参数 变量
下面是本人通俗的理解,如有不对之处,希望指正 info参数的设置:在info中用到的参数都在server的专门的配置文件中(最好以parma)结尾 下面的GLOBAl就是全局的,$开头的是系统级变量,$$开头的变量是自定义变量。如果是在session中或者mapping中用到的变量就是局部变量,那就把global换成对应的session或者mapping名字。
[GLOBAL] $Par
python 解析unicode字符串为utf8编码字符串
酷的飞上天空
unicode
php返回的json字符串如果包含中文,则会被转换成\uxx格式的unicode编码字符串返回。
在浏览器中能正常识别这种编码,但是后台程序却不能识别,直接输出显示的是\uxx的字符,并未进行转码。
转换方式如下
>>> import json
>>> q = '{"text":"\u4
Hibernate的总结
永夜-极光
Hibernate
1.hibernate的作用,简化对数据库的编码,使开发人员不必再与复杂的sql语句打交道
做项目大部分都需要用JAVA来链接数据库,比如你要做一个会员注册的 页面,那么 获取到用户填写的 基本信后,你要把这些基本信息存入数据库对应的表中,不用hibernate还有mybatis之类的框架,都不用的话就得用JDBC,也就是JAVA自己的,用这个东西你要写很多的代码,比如保存注册信
SyntaxError: Non-UTF-8 code starting with '\xc4'
随便小屋
python
刚开始看一下Python语言,传说听强大的,但我感觉还是没Java强吧!
写Hello World的时候就遇到一个问题,在Eclipse中写的,代码如下
'''
Created on 2014年10月27日
@author: Logic
'''
print("Hello World!");
运行结果
SyntaxError: Non-UTF-8
学会敬酒礼仪 不做酒席菜鸟
aijuans
菜鸟
俗话说,酒是越喝越厚,但在酒桌上也有很多学问讲究,以下总结了一些酒桌上的你不得不注意的小细节。
细节一:领导相互喝完才轮到自己敬酒。敬酒一定要站起来,双手举杯。
细节二:可以多人敬一人,决不可一人敬多人,除非你是领导。
细节三:自己敬别人,如果不碰杯,自己喝多少可视乎情况而定,比如对方酒量,对方喝酒态度,切不可比对方喝得少,要知道是自己敬人。
细节四:自己敬别人,如果碰杯,一
《创新者的基因》读书笔记
aoyouzi
读书笔记 《创新者的基因》
创新者的基因
创新者的“基因”,即最具创意的企业家具备的五种“发现技能”:联想,观察,实验,发问,建立人脉。
第一部分破坏性创新,从你开始
第一章破坏性创新者的基因
如何获得启示:
发现以下的因素起到了催化剂的作用:(1) -个挑战现状的问题;(2)对某项技术、某个公司或顾客的观察;(3) -次尝试新鲜事物的经验或实验;(4)与某人进行了一次交谈,为他点醒
表单验证技术
百合不是茶
JavaScript DOM对象 String对象 事件
js最主要的功能就是验证表单,下面是我对表单验证的一些理解,贴出来与大家交流交流 ,数显我们要知道表单验证需要的技术点, String对象,事件,函数
一:String对象;通常是对字符串的操作;
1,String的属性;
字符串.length;表示该字符串的长度;
var str= "java"
web.xml配置详解之context-param
bijian1013
java servlet web.xml context-param
一.格式定义:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>contextConfigLocationValue></param-value>
</context-param>
作用:该元
Web系统常见编码漏洞(开发工程师知晓)
Bill_chen
sql PHP Web fckeditor 脚本
1.头号大敌:SQL Injection
原因:程序中对用户输入检查不严格,用户可以提交一段数据库查询代码,根据程序返回的结果,
获得某些他想得知的数据,这就是所谓的SQL Injection,即SQL注入。
本质:
对于输入检查不充分,导致SQL语句将用户提交的非法数据当作语句的一部分来执行。
示例:
String query = "SELECT id FROM users
【MongoDB学习笔记六】MongoDB修改器
bit1129
mongodb
本文首先介绍下MongoDB的基本的增删改查操作,然后,详细介绍MongoDB提供的修改器,以完成各种各样的文档更新操作 MongoDB的主要操作
show dbs 显示当前用户能看到哪些数据库
use foobar 将数据库切换到foobar
show collections 显示当前数据库有哪些集合
db.people.update,update不带参数,可
提高职业素养,做好人生规划
白糖_
人生
培训讲师是成都著名的企业培训讲师,他在讲课中提出的一些观点很新颖,在此我收录了一些分享一下。注:讲师的观点不代表本人的观点,这些东西大家自己揣摩。
1、什么是职业规划:职业规划并不完全代表你到什么阶段要当什么官要拿多少钱,这些都只是梦想。职业规划是清楚的认识自己现在缺什么,这个阶段该学习什么,下个阶段缺什么,又应该怎么去规划学习,这样才算是规划。
国外的网站你都到哪边看?
bozch
技术 网站 国外
学习软件开发技术,如果没有什么英文基础,最好还是看国内的一些技术网站,例如:开源OSchina,csdn,iteye,51cto等等。
个人感觉如果英语基础能力不错的话,可以浏览国外的网站来进行软件技术基础的学习,例如java开发中常用的到的网站有apache.org 里面有apache的很多Projects,springframework.org是spring相关的项目网站,还有几个感觉不错的
编程之美-光影切割问题
bylijinnan
编程之美
package a;
public class DisorderCount {
/**《编程之美》“光影切割问题”
* 主要是两个问题:
* 1.数学公式(设定没有三条以上的直线交于同一点):
* 两条直线最多一个交点,将平面分成了4个区域;
* 三条直线最多三个交点,将平面分成了7个区域;
* 可以推出:N条直线 M个交点,区域数为N+M+1。
关于Web跨站执行脚本概念
chenbowen00
Web 安全 跨站执行脚本
跨站脚本攻击(XSS)是web应用程序中最危险和最常见的安全漏洞之一。安全研究人员发现这个漏洞在最受欢迎的网站,包括谷歌、Facebook、亚马逊、PayPal,和许多其他网站。如果你看看bug赏金计划,大多数报告的问题属于 XSS。为了防止跨站脚本攻击,浏览器也有自己的过滤器,但安全研究人员总是想方设法绕过这些过滤器。这个漏洞是通常用于执行cookie窃取、恶意软件传播,会话劫持,恶意重定向。在
[开源项目与投资]投资开源项目之前需要统计该项目已有的用户数
comsci
开源项目
现在国内和国外,特别是美国那边,突然出现很多开源项目,但是这些项目的用户有多少,有多少忠诚的粉丝,对于投资者来讲,完全是一个未知数,那么要投资开源项目,我们投资者必须准确无误的知道该项目的全部情况,包括项目发起人的情况,项目的维持时间..项目的技术水平,项目的参与者的势力,项目投入产出的效益.....
oracle alert log file(告警日志文件)
daizj
oracle 告警日志文件 alert log file
The alert log is a chronological log of messages and errors, and includes the following items:
All internal errors (ORA-00600), block corruption errors (ORA-01578), and deadlock errors (ORA-00060)
关于 CAS SSO 文章声明
denger
SSO
由于几年前写了几篇 CAS 系列的文章,之后陆续有人参照文章去实现,可都遇到了各种问题,同时经常或多或少的收到不少人的求助。现在这时特此说明几点:
1. 那些文章发表于好几年前了,CAS 已经更新几个很多版本了,由于近年已经没有做该领域方面的事情,所有文章也没有持续更新。
2. 文章只是提供思路,尽管 CAS 版本已经发生变化,但原理和流程仍然一致。最重要的是明白原理,然后
初二上学期难记单词
dcj3sjt126com
english word
lesson 课
traffic 交通
matter 要紧;事物
happy 快乐的,幸福的
second 第二的
idea 主意;想法;意见
mean 意味着
important 重要的,重大的
never 从来,决不
afraid 害怕 的
fifth 第五的
hometown 故乡,家乡
discuss 讨论;议论
east 东方的
agree 同意;赞成
bo
uicollectionview 纯代码布局, 添加头部视图
dcj3sjt126com
Collection
#import <UIKit/UIKit.h>
@interface myHeadView : UICollectionReusableView
{
UILabel *TitleLable;
}
-(void)setTextTitle;
@end
#import "myHeadView.h"
@implementation m
N 位随机数字串的 JAVA 生成实现
FX夜归人
java Math 随机数 Random
/**
* 功能描述 随机数工具类<br />
* @author FengXueYeGuiRen
* 创建时间 2014-7-25<br />
*/
public class RandomUtil {
// 随机数生成器
private static java.util.Random random = new java.util.R
Ehcache(09)——缓存Web页面
234390216
ehcache 页面缓存
页面缓存
目录
1 SimplePageCachingFilter
1.1 calculateKey
1.2 可配置的初始化参数
1.2.1 cach
spring中少用的注解@primary解析
jackyrong
primary
这次看下spring中少见的注解@primary注解,例子
@Component
public class MetalSinger implements Singer{
@Override
public String sing(String lyrics) {
return "I am singing with DIO voice
Java几款性能分析工具的对比
lbwahoo
java
Java几款性能分析工具的对比
摘自:http://my.oschina.net/liux/blog/51800
在给客户的应用程序维护的过程中,我注意到在高负载下的一些性能问题。理论上,增加对应用程序的负载会使性能等比率的下降。然而,我认为性能下降的比率远远高于负载的增加。我也发现,性能可以通过改变应用程序的逻辑来提升,甚至达到极限。为了更详细的了解这一点,我们需要做一些性能
JVM参数配置大全
nickys
jvm 应用服务器
JVM参数配置大全
/usr/local/jdk/bin/java -Dresin.home=/usr/local/resin -server -Xms1800M -Xmx1800M -Xmn300M -Xss512K -XX:PermSize=300M -XX:MaxPermSize=300M -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=5 -
搭建 CentOS 6 服务器(14) - squid、Varnish
rensanning
varnish
(一)squid
安装
# yum install httpd-tools -y
# htpasswd -c -b /etc/squid/passwords squiduser 123456
# yum install squid -y
设置
# cp /etc/squid/squid.conf /etc/squid/squid.conf.bak
# vi /etc/
Spring缓存注解@Cache使用
tom_seed
spring
参考资料
http://www.ibm.com/developerworks/cn/opensource/os-cn-spring-cache/
http://swiftlet.net/archives/774
缓存注解有以下三个:
@Cacheable @CacheEvict @CachePut
dom4j解析XML时出现"java.lang.noclassdeffounderror: org/jaxen/jaxenexception"错误
xp9802
java.lang.NoClassDefFoundError: org/jaxen/JaxenExc
关键字: java.lang.noclassdeffounderror: org/jaxen/jaxenexception
使用dom4j解析XML时,要快速获取某个节点的数据,使用XPath是个不错的方法,dom4j的快速手册里也建议使用这种方式
执行时却抛出以下异常:
Exceptio