170

用 .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. 抛出和捕获非 System.Exception 类型
  19. 继承枚举(未验证)
  20. 您可以将字节数组视为(小 4 倍)整数数组。
  21. 您可以让一个字段/方法/属性/事件都具有相同的名称(未验证)。
  22. 您可以从它自己的 catch 块分支回到 try 块。
  23. 您可以访问 famandassem 访问说明符(protected internal是 famassem,但现在在 C# 7.2 和 VB 15.5 中允许)
  24. 直接访问<Module>用于定义全局函数的类或模块初始化程序。
  25. 创建和使用基于 1 的非零边界数组
  26. 创建开放实例和封闭静态委托,以及 getter/setter 的委托
  27. 在不使用临时变量的情况下交换两个值
  28. 显式实现任意名称的接口,实现两个接口功能合二为一(VB可以实现)
  29. 声明vtfixup(相当于externC 语言)
  30. 指定任意modoptmodreq
4

20 回答 20

34

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

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

或者

callvirt int32 ...
于 2009-02-12T16:06:30.573 回答
29

包括 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,它必须手动实现。(抖动也可以自由地自行进行这种优化,但这是另一个问题。)

于 2009-02-12T16:07:36.110 回答
21

在 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
于 2009-02-12T16:09:38.210 回答
20

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

于 2009-02-12T16:14:06.767 回答
18

哦,我当时没有发现这个。(如果您添加 jon-skeet 标签,则更有可能,但我不经常检查它。)

看起来你已经有了很好的答案。此外:

  • 您无法掌握 C# 中值类型的盒装版本。你可以在 C++/CLI
  • 您不能在 C# 中进行尝试/错误(“错误”类似于“捕获所有内容并在块的末尾重新抛出”或“最终但仅在失败时”)
  • 有很多 C# 禁止但合法 IL 的名称
  • IL 允许您为值类型定义自己的无参数构造函数
  • 您不能在 C# 中使用“raise”元素定义事件。(在 VB 中,您必须自定义事件,但“默认”事件不包括一个。)
  • CLR 允许某些转换,但 C# 不允许。如果你object在 C# 中通过,这些有时会起作用。有关示例,请参见uint[]/int[] SO question

如果我想到其他任何事情,我会补充这一点......

于 2009-02-26T16:06:58.060 回答
17

CLR 已经支持泛型协/逆变,但 C# 直到 4.0 才获得此功能

于 2009-02-12T15:57:54.417 回答
14

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

于 2009-03-13T16:53:57.467 回答
10

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

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()语法发出非虚拟调用。在上面,这将是MyClass.ToString().

于 2009-02-23T16:24:42.053 回答
9

使用 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(注意When ShouldCatch(ex) = True捕获子句):

Try
   Foo()
Catch ex As CustomBaseException When ShouldCatch(ex)
   Console.WriteLine("Caught exception!")
End Try
于 2009-02-12T16:11:49.080 回答
9

在 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 中做到这一点

于 2011-01-18T11:02:19.080 回答
8

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

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

于 2009-10-16T13:08:52.863 回答
7

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

Transient Pointers
您可以使用临时指针,它们是指向托管类型的指针,但保证不会在内存中移动,因为它们不在托管堆中。不完全确定如何在不弄乱非托管代码的情况下有效地使用它,但它不会仅通过诸如 stackalloc 之类的东西直接暴露给其他语言。

<Module>
如果你愿意,你可以在课堂上捣乱(你可以通过反射来做到这一点,而不需要 IL)

.emitbyte

15.4.1.1 .emitbyte 指令 MethodBodyItem ::= ... | .emitbyte Int32 该指令导致一个无符号的 8 位值直接发送到该方法的 CIL 流中,在该指令出现的位置。[注意:.emitbyte 指令用于生成测试。生成常规程序时不需要它。尾注]

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

阅读规范我相信你会发现更多。

于 2009-02-26T22:53:00.737 回答
6

您可以破解方法覆盖 co/contra-variance,这是 C# 不允许的(这与通用方差不同!)。我有更多关于在此处实施的信息,以及第 1部分和第 2部分

于 2010-08-03T15:49:39.703 回答
4

这里还有一些:

  1. 您可以在委托中拥有额外的实例方法。
  2. 代表可以实现接口。
  3. 您可以在委托和接口中拥有静态成员。
于 2009-06-03T04:49:41.030 回答
4

I think the one I kept wishing for (with entirely the wrong reasons) was inheritance in Enums. It doesn't seem like a hard thing to do in SMIL (since Enums are just classes) but it's not something the C# syntax wants you to do.

于 2009-03-02T15:19:04.913 回答
3

20)您可以将字节数组视为(4x 小)整数数组。

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

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

===

我没有足够的 stackoverflow street credz 来编辑问题并将其添加到列表中作为 #20,如果其他人可以这样做会膨胀;-)

于 2009-10-11T16:17:04.653 回答
3

混淆器使用的东西 - 你可以让一个字段/方法/属性/事件都具有相同的名称。

于 2009-10-16T12:25:26.537 回答
2

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

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

公共类 YourCustomDelegate : MulticastDelegate { }

于 2011-02-17T05:58:34.217 回答
2

枚举继承实际上是不可能的:

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

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

于 2010-12-06T17:58:51.363 回答
2

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

于 2011-02-17T06:11:39.687 回答