你能在 MSIL 中做什么在 C # 或 VB.NET 中不能做的事情?

所有代码都写好了。NET 语言编译成 MSIL,但是是否存在只能直接使用 MSIL 执行的特定任务/操作?

让我们用 MSIL 来做一些比 C # 、 VB.NET、 F # 、 j # 或其他任何.NET 语言更容易的事情吧。

到目前为止,我们有这个:

  1. 尾递归
  2. 通用协同/反差(C # 4和 VB 10允许)
  3. 只有返回类型不同的重载
  4. 重写访问修饰符
  5. 有一个不能从 System.Object 继承的类
  6. 过滤异常(在 VB 和 C # 6中允许)
  7. 调用当前静态类类型的虚方法。
  8. 获取值类型的装箱版本的句柄。
  9. 尝试/犯错。
  10. 禁用名称的使用。
  11. 为值类型 定义您自己的无参数构造函数。
  12. 使用 raise元素定义事件。
  13. CLR 允许但 C # 不允许的一些转换。
  14. 制作一个非 main()方法作为 .entrypoint
  15. 直接使用本地 int和本地 unsigned int类型。
  16. 使用瞬态指针
  17. MethodBodyItem 中的 emitbyte 指令
  18. 抛出和捕获非系统异常类型
  19. 继承枚举(未验证)
  20. 您可以将字节数组视为整型数组(比整型数组小4倍)。
  21. 可以有一个字段/方法/属性/事件都具有相同的名称(未经验证)。
  22. 您可以从它自己的 catch 块分支回 try 块。
  23. 您可以访问 famandassem 访问说明符(protected internal是 fam或者assem,但是现在在 C # 7.2和 VB 15.5中允许)
  24. 直接访问 <Module>类以定义全局函数或模块初始值设定项。
  25. 创建和使用非零绑定的基于1的数组
  26. 创建开放实例和闭合静态委托,以及 getters/setter 的委托
  27. 不使用临时变量交换两个值
  28. 使用任意名称实现显式接口,并在一个接口中实现两个接口函数(可以在 VB 中实现)
  29. 声明 vtfixup(相当于 C 中的 extern)
  30. 指定任意 modoptmodreq
10383 次浏览

CLR 已经支持通用协变/逆变,但是 C # 要到4.0版才能获得这个特性

MSIL 允许仅在返回类型上有所不同的重载,因为

call void [mscorlib]System.Console::Write(string)

或者

callvirt int32 ...

包括 C # 和 VB 在内的大多数.Net 语言都不使用 MSIL 代码的尾部递归特性。

尾递归是函数式语言中常见的一种优化方法。当方法 A 通过返回方法 B 的值结束时发生,这样一旦对方法 B 进行调用,方法 A 的堆栈就可以被释放。

MSIL 代码显式地支持尾递归,对于某些算法来说,这可能是要进行的重要优化。但是由于 C # 和 VB 不生成这样做的指令,所以必须手动完成(或者使用 F # 或其他语言)。

下面是一个如何在 C # 中手动实现尾递归的例子:

private static int RecursiveMethod(int myParameter)
{
// Body of recursive method
if (BaseCase(details))
return result;
// ...


return RecursiveMethod(modifiedParameter);
}


// Is transformed into:


private static int RecursiveMethod(int myParameter)
{
while (true)
{
// Body of recursive method
if (BaseCase(details))
return result;
// ...


myParameter = modifiedParameter;
}
}

通常的做法是通过将本地数据从硬件堆栈移动到堆分配的堆栈数据结构上来消除递归。在如上所示的尾部调用递归消除中,堆栈被完全消除,这是一个非常好的优化。此外,返回值不必在长调用链中遍历,而是直接返回。

但是,无论如何,CIL 提供了这个特性作为语言的一部分,但是对于 C # 或 VB,它必须手动实现。(抖动也可以自行进行这种优化,但这是另一个完全不同的问题。)

在 MSIL 中,可以有一个不能从 System.Object 继承的类。

示例代码: 使用 ilasm.exe 更新:编译它,必须使用“/NOAUTOINHERIT”来防止汇编程序自动继承。

// Metadata version: v2.0.50215
.assembly extern mscorlib
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 )                         // .z\V.4..
.ver 2:0:0:0
}
.assembly sample
{
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 )
.hash algorithm 0x00008004
.ver 0:0:0:0
}
.module sample.exe
// MVID: {A224F460-A049-4A03-9E71-80A36DBBBCD3}
.imagebase 0x00400000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003       // WINDOWS_CUI
.corflags 0x00000001    //  ILONLY
// Image base: 0x02F20000




// =============== CLASS MEMBERS DECLARATION ===================


.class public auto ansi beforefieldinit Hello
{
.method public hidebysig static void  Main(string[] args) cil managed
{
.entrypoint
// Code size       13 (0xd)
.maxstack  8
IL_0000:  nop
IL_0001:  ldstr      "Hello World!"
IL_0006:  call       void [mscorlib]System.Console::WriteLine(string)
IL_000b:  nop
IL_000c:  ret
} // end of method Hello::Main
} // end of class Hello

使用 IL 和 VB.NET,您可以在捕获异常时添加过滤器,但是 C # v3不支持这个特性。

这个 VB.NET 示例取自 http://blogs.msdn.com/clrteam/archive/2009/02/05/catch-rethrow-and-filters-why-you-should-care.aspx(请注意 Catch 子句中的 When ShouldCatch(ex) = True) :

Try
Foo()
Catch ex As CustomBaseException When ShouldCatch(ex)
Console.WriteLine("Caught exception!")
End Try

组合 protectedinternal访问修饰符是可能的。在 C # 中,如果编写 protected internal,则可以从程序集和派生类访问成员。通过 MSIL,您可以获得可从程序集 只有中的派生类访问的成员。(我认为这会非常有用!)

对于虚方法调用,IL 具有 callcallvirt之间的区别。通过使用前者,您可以强制调用 当前静态类类型的虚方法,而不是动态类类型中的虚函数。

C # 无法做到这一点:

abstract class Foo {
public void F() {
Console.WriteLine(ToString()); // Always a virtual call!
}


public override string ToString() { System.Diagnostics.Debug.Assert(false); }
};


sealed class Bar : Foo {
public override string ToString() { return "I'm called!"; }
}

VB 和 IL 一样,可以使用 MyClass.Method()语法发出非虚拟调用。

我当时没注意到这个。(如果你加上 Jon-skeet 标签,这种可能性更大,但我不经常检查它。)

看来你已经有了不错的答案。另外:

  • 在 C # 中无法获得值类型的盒装版本的句柄,但在 C + +/CLI 中可以
  • 不能在 C # 中执行 try/fault (“ fault”类似于“ catch everything and rethrow at the end of the block”或者“ finally but only on fault”)
  • 有很多名字是 C # 禁止的,但是是合法的 IL
  • IL 允许你 为值类型定义自己的无参数构造函数
  • 在 C # 中不能使用“ rise”元素定义事件。(在 VB 中,用于自定义事件,但“默认”事件不包括一个。)
  • CLR 允许一些转换,但 C # 不允许。如果你通过 C # 中的 object,这些有时会起作用。有关示例,请参见 所以问题

如果我想起什么,我会补充一下。

Native types
可以直接使用本机 int 和本机无符号 int 类型(在 c # 中,只能使用不同的 IntPtr)。

Transient Pointers
您可以使用瞬态指针,它们是指向托管类型的指针,但是保证不会在内存中移动,因为它们不在托管堆中。不完全确定如何在不干扰非托管代码的情况下有效地使用它,但是只有通过 stackalloc 之类的东西才能将它直接公开给其他语言。

<Module>
如果您愿意,您可以随意处理这个类(您可以通过反射来做到这一点,而不需要 IL)

.emitbyte

15.4.1.1. emitbyte 指令 MethodBodyItem: : = ... | . emitbyte 此指令导致一个 要发出的无符号8位值 直接进入 CIL 流的 方法,则在 指令出现 . emitbyte 指令用于 产生测试。这是不需要的 在生成常规程序方面。结束 注]

.entrypoint
在这方面您有更多的灵活性,例如,您可以将其应用于不称为 Main 的方法。

读一读 规格,我相信你会找到更多。

我认为我一直希望(出于完全错误的理由)在 Enums 继承遗产。在 SMIL 中这似乎不是一件困难的事情(因为 Enums 只是一些类) ,但是 C # 语法并不希望您这样做。

在 IL 中,您可以抛出和捕获任何类型,而不仅仅是从 System.Exception派生的类型。

这里还有一些:

  1. 委托中可以有额外的实例方法。
  2. 委托可以实现接口。
  3. 可以在委托和接口中使用静态成员。

20)你可以把一个字节数组看作是一个整型数组(比整型数组小4倍)。

我最近使用它来实现一个快速的 XOR,因为 CLR XOR 函数对 int 进行操作,而我需要对一个字节流执行 XOR。

测量得到的代码比 C # 中的等效代码快约10倍(对每个字节执行 XOR)。

===

我没有足够的 stackoverflow street credz 来编辑这个问题,并将其添加到列表中为 # 20,如果其他人可以的话,那将是巨大的; -)

模糊处理器使用的东西-你可以有一个字段/方法/属性/事件都有相同的名称。

据我所知,没有办法直接在 C # 中创建模块初始化器(整个模块的静态构造函数) :

Http://blogs.msdn.com/junfeng/archive/2005/11/19/494914.aspx

您可以修改方法,覆盖 C # 不允许的 co/conta-variance (这与泛型方差不一样!).关于实现这个 给你,以及部分 12,我有更多的信息

Enum 继承实际上是不可能的:

可以从 Enum 类继承。但是结果的行为并不特别像 Enum。它的行为甚至不像一个值类型,而像一个普通类。奇怪的是: IsEnum: True,IsValueType: True,IsClass: False

但是这并不特别有用(除非您想混淆某个人或运行时本身)

在 try/catch 中,您可以从自己的 catch 块中重新输入 try 块:

.try {
// ...


MidTry:
// ...


leave.s RestOfMethod
}
catch [mscorlib]System.Exception {
leave.s MidTry  // branching back into try block!
}


RestOfMethod:
// ...

AFAIK 你不能在 C # 或 VB 中这样做

也可以在 IL 中从 System.Multicast 委托派生一个类,但是在 C # 中不能这样做:

//下面的类定义是非法的:

公开课代表: 多重代理 { }

您还可以在 IL 中定义模块级(也称为全局)方法,相反,C # 只允许您定义方法,只要它们附加到至少一种类型。