当前位置:  开发笔记 > 编程语言 > 正文

你在MSIL中可以做些什么,你不能用C#或VB.NET做什么?

如何解决《你在MSIL中可以做些什么,你不能用C#或VB.NET做什么?》经验,为你挑选了13个好方法。

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

让我们在MSIL中比C#,VB.NET,F#,j#或任何其他.NET语言更容易完成.

到目前为止我们有这个:

    尾递归

    通用Co/Contravariance

    重载仅在返回类型上有所不同

    覆盖访问修饰符

    有一个不能从System.Object继承的类

    过滤的异常(可以在vb.net中完成)

    调用当前静态类类型的虚方法.

    获取值类型的盒装版本的句柄.

    做一个尝试/错误.

    使用禁用名称.

    为值类型定义自己的无参数构造函数.

    使用raise元素定义事件.

    CLR允许一些转换,但C#不允许转换.

    做一个非main()方法作为.entrypoint.

    直接使用本机int和本机unsigned int类型.

    玩瞬态指针

    MethodBodyItem中的emitbyte指令

    抛出并捕获非System.Exception类型

    继承枚举(未验证)

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

    您可以使字段/方法/属性/事件具有相同的名称(未验证).

    您可以从自己的catch块分支回try块.

    您可以访问famandassem访问说明符(protected internal是fam assem)

    直接访问类以定义全局函数或模块初始值设定项.

Anton Gogole.. 34

MSIL允许重载仅因返回类型而异

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

要么

callvirt int32 ...

如果除了返回类型之外两个方法相同,可以从C#或vb.net调用吗? (12认同)

这太棒了.谁不想让回程超载? (8认同)

你怎么知道这种东西?:) (5认同)


Jeffrey L Wh.. 28

大多数.Net语言(包括C#和VB)都不使用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,它必须手动实现.(抖动也可以自由地进行优化,但这是另一个问题.)



1> Anton Gogole..:

MSIL允许重载仅因返回类型而异

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

要么

callvirt int32 ...


如果除了返回类型之外两个方法相同,可以从C#或vb.net调用吗?
这太棒了.谁不想让回程超载?
你怎么知道这种东西?:)

2> Jeffrey L Wh..:

大多数.Net语言(包括C#和VB)都不使用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,它必须手动实现.(抖动也可以自由地进行优化,但这是另一个问题.)


理查德,我不确定你的意思.F#肯定会排出尾巴.呼叫前缀,几乎到处都是.检查IL是这样的:"let print x = print_any x".
@Abel:虽然处理器体系结构与*理论*无关,但它与*practice*无关,因为不同体系结构的不同JIT在.NET中具有不同的尾递归规则.换句话说,你可以*非常容易*有一个程序在x86上爆炸但在x64上没有爆炸.仅仅因为尾递归*可以在两种情况下实现并不意味着*是*.请注意,这个问题专门针对.NET.
在特定情况下,C#实际上在x64下执行尾调用:http://community.bartdesmet.net/blogs/bart/archive/2010/07/07/the-case-of-the-failed-demo-stackoverflowexception-on-x64的.aspx.

3> Ramesh..:

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

示例代码:使用ilasm.exe UPDATE编译它:您必须使用"/ 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


@Ramesh:哎呀,你说得对.我说那时它违反了标准规范,不应该使用.反射器甚至没有加载.但是,*可以*用ilasm完成.我想知道为什么它在那里.
(啊,我看到/ noautoinherit位在我的评论之后被添加了.至少我觉得在之前没有意识到它有点好......)
@Jon Skeet - 尽管如此,请你帮我理解NOAUTOINHERIT的含义.MSDN指定"在未指定基类时禁用对象的默认继承.在.NET Framework 2.0版中新增."
@Michael - 问题是关于MSIL而不是普通中间语言.我同意这在CIL中可能无法实现,但它仍适用于MSIL

4> yatima2975..:

可以组合使用protectedinternal访问修饰符.在C#中,如果编写protected internal成员,则可以从程序集和派生类访问.通过MSIL你可以得到一个成员是从组件内的派生类访问.(我认为这可能非常有用!)


它现在是在C#7.1(https://github.com/dotnet/csharplang/issues/37)中实现的候选者,访问修饰符是"私有保护的".
它已作为C#7.2的一部分发布:https://blogs.msdn.microsoft.com/dotnet/2017/11/15/welcome-to-c-7-2-and-span/

5> Jon Skeet..:

哦,我当时没有发现这一点.(如果你添加jon-skeet标签更有可能,但我不经常检查它.)

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

您无法处理C#中值类型的盒装版本.您可以在C++/CLI中

你不能在C#中做一个尝试/错误("故障"就像是"抓住一切并在块的末尾重新抛出"或"最后但仅在失败时")

C#禁止使用许多名称但合法的IL

IL允许您为值类型定义自己的无参数构造函数.

您无法在C#中使用"raise"元素定义事件.(在VB中,您必须使用自定义事件,但"默认"事件不包括一个.)

CLR允许一些转换,但C#不允许转换.如果你通过objectC#,这些有时会起作用.有关示例,请参阅uint []/int [] SO问题.

如果我想到别的什么,我会加上这个......


@George:适用于关键字,但不适用于所有有效的IL名称.尝试在C#中指定`<> a`作为名称...
啊jon-skeet标签,我知道我错过了什么!

6> ermau..:

CLR已经支持通用协同/反演,但C#直到4.0才获得此功能

C#4.0功能

CO /逆变



7> Daniel Earwi..:

在IL中,你可以抛出并捕获任何类型,而不仅仅是派生类型System.Exception.


你也可以在C#中做到这一点,在catch语句中没有括号的`try` /`catch`你也会捕获非类似异常的异常.但是,当你从'Exception`继承时,确实只能投掷.

8> Konrad Rudol..:

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!"; }
}

与IL一样,VB可以使用MyClass.Method()语法发出非虚拟调用.在上面,这将是MyClass.ToString().



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在Catch条款):

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


请rRemove` = True`,它让我的眼睛流血!
paSHIr,我相信他在谈论它的减少
@Frank Schwieterman:捕获和重新抛出异常与阻止捕获异常之间存在差异.过滤器在任何嵌套的"finally"语句之前运行,因此在运行过滤器时,导致异常的情况仍然存在.如果人们期望抛出大量的SocketException,那么人们会想要相对默默地捕获,但是其中一些会发出信号,能够在抛出有问题的状态时检查状态可能非常有用.

10> thecoop..:

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


我可以看出为什么这被省略了 - 它有一种独特的"GOTO"气味
听起来很像VB.NET中的[On Error Resume Next](https://msdn.microsoft.com/en-us/library/5hsw66as.aspx)

11> yoyoyoyosef..:

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

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



12> ShuggyCoUk..:

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

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


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

.emitbyte

15.4.1.1 .emitbyte指令MethodBodyItem :: = ... | .emitbyte Int32此指令使无符号的8位值直接发送到方法的CIL流中,在指令出现的位置.[注意:.emitbyte指令用于生成测试.生成常规程序不需要它.结束说明]

.entrypoint
您可以在此方面获得更多灵活性,例如,您可以将其应用于未称为Main的方法.

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



13> thecoop..:

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

推荐阅读
ERIK又
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有