关于.net:在CIL或VB.NET中你不能用MSIL做什么?

What can you do in MSIL that you cannot do in C# or VB.NET?

所有用.NET语言编写的代码都编译为MSIL,但是否有只能直接使用MSIL的特定任务/操作?

让我们在MSIL中做的事情比C、VB.NET、F、J或任何其他.NET语言都容易。

到目前为止,我们有:

  • 尾部递归
  • 一般协变/逆变
  • 仅返回类型不同的重载
  • 重写访问修饰符
  • 具有不能从System.Object继承的类
  • 筛选的异常(可以在vb.net中完成)
  • 调用当前静态类类型的虚拟方法。
  • 获取值类型的装箱版本的句柄。
  • 做一个尝试/错误。
  • 禁止使用的名称。
  • 为值类型定义自己的无参数构造函数。
  • 使用raise元素定义事件。
  • 一些转换是由clr允许的,但不是由c_允许的。
  • 制作一个非main()方法作为.entrypoint方法。
  • 直接使用原生int和原生unsigned int类型。
  • 玩瞬时指针
  • methodBodyItem中的emitByte指令
  • 引发和捕获非System.Exception类型
  • 继承枚举(未验证)
  • 您可以将一个字节数组视为一个整数数组(小4倍)。
  • 您可以让字段/方法/属性/事件都具有相同的名称(未验证)。
  • 您可以从自己的catch块分支回try块。
  • 您可以访问famandassem访问说明符(protected internal是famorasem)
  • 直接访问类以定义全局函数或模块初始值设定项。

  • msil允许仅在返回类型上不同的重载,因为

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

    1
    callvirt int32 ...


    大多数.NET语言(包括C和VB)都不使用MSIL代码的尾部递归功能。

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

    MSIL代码显式支持尾部递归,对于某些算法,这可能是一个重要的优化。但由于C和VB不生成执行此操作的指令,因此必须手动执行(或使用F或其他语言)。

    下面是一个示例,说明如何在C中手动实现尾部递归:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    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"以防止汇编程序自动继承。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    // 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


    可以结合protectedinternal访问修饰符。在C中,如果编写protected internal,则可以从程序集和派生类访问成员。通过msil,您可以获取仅从程序集中的派生类访问的成员。(我认为这很有用!)


    哦,我当时没发现这个。(如果你加上jon-skeet标签,可能更容易,但我不会经常检查。)

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

    • 在C中,无法获得值类型的装箱版本的句柄。你可以在C++/CLI中使用
    • 你不能在C中尝试/犯错("犯错"类似于"抓住每件事,在块的末尾重新思考"或"最后但只在失败时")。
    • 有很多名字被C禁止,但法律上是不允许的。
    • IL允许您为值类型定义自己的无参数构造函数。
    • 不能用c_中的"raise"元素定义事件。(在VB中,您必须为自定义事件设置,但"默认"事件不包括一个。)
    • 有些转换是由clr允许的,但C不允许。如果你通过c中的object,这些有时会起作用。例如,请参见uint[]/int[]so问题。

    如果我想到其他的事情,我会加上这个…


    clr已经支持通用的co/contravariance,但是c直到4.0才获得这个特性。

    • C 4特征
    • 协变/逆变


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


    IL在虚拟方法调用中区分了callcallvirt。通过使用前者,可以强制调用当前静态类类型的虚方法,而不是动态类类型中的虚函数。

    C没有办法做到这一点:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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()


    在try/catch中,可以从自己的catch块重新输入try块。所以,你可以这样做:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    .try {
        // ...

      MidTry:
        // ...

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

    RestOfMethod:
        // ...

    Afaik你不能在C或VB中这样做


    使用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)。

    1
    2
    3
    4
    5
    Try
       Foo()
    Catch ex As CustomBaseException When ShouldCatch(ex)
       Console.WriteLine("Caught exception!")
    End Try


    据我所知,无法在C中直接生成模块初始值设定项(整个模块的静态构造函数):

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


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

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

    如果你愿意的话,你可以在课堂上乱搞(你可以通过反思来做到这一点,而不需要IL)

    .emitbyte

    15.4.1.1 The .emitbyte directive MethodBodyItem ::= … | .emitbyte
    Int32 This directive causes an
    unsigned 8-bit value to be emitted
    directly into the CIL stream of the
    method, at the point at which the
    directive appears. [Note: The
    .emitbyte directive is used for
    generating tests. It is not required
    in generating regular programs. end
    note]

    .entrypoint您在这方面有点灵活性,例如,您可以将其应用于未调用main的方法。

    读一读说明书,我相信你会发现更多的。


    您可以使用hack方法覆盖co/contra方差,这是c_不允许的(这与一般方差不同!).我有更多关于在这里实现这一点的信息,以及第1部分和第2部分


    还有一些:

  • 在委托中可以有额外的实例方法。
  • 委托可以实现接口。
  • 在委托和接口中可以有静态成员。

  • 我想我一直希望(完全错误的原因)的是遗赠。在SMIL中似乎不难做到(因为枚举只是类),但C语法并不希望您做到这一点。


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

    我最近用它来做一个快速的XOR实现,因为clr xor函数在ints上操作,我需要在字节流上执行XOR。

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

    = =

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


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


    枚举继承实际上不可能:

    可以从枚举类继承。但结果并不像枚举那样具体。它的行为甚至不像一个值类型,而是像一个普通类。范围是:isEnum:真,isValueType:真,isClass:假

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


    您还可以从IL中的system.multicast委托派生类,但在C中不能这样做:

    // The following class definition is illegal:

    public class YourCustomDelegate :
    MulticastDelegate
    {
    }


    您还可以在IL中定义模块级(aka global)方法,而C_则相反,只允许您定义方法,只要它们连接到至少一个类型。