IL中的"最终"是什么意思?

Lor*_*tté 8 c# clr overriding cil

使用ildasm/ilasm时,您可以观察编译器生成的MSIL/CIL代码(例如C#编译器),在某些情况下,您可以看到有标记为的方法virtual final.

final在这种情况下意味着什么?我的猜测是它意味着"此方法无法被覆盖",因此即使此方法在虚拟表中有一个插槽,该插槽也不能被派生类中的方法覆盖.

但是怎么样?它在实践中如何运作?

它只是由编译器强制执行(例如,如果您尝试覆盖密封方法,它将失败并出现编译错误)或CLR(例如,当您尝试重写方法时,CLR抛出),或两者都执行?有没有合法的方法"删除",绕过这个final,即编写一个覆盖此方法的派生类?

更新:我可能已经找到一种方法(见我自己的答案),但我不知道这一切是合法的,支持的,标准的,所以,我已经张贴在这个新的(但相关的)问题,另外一个问题在这里

我愿意接受评论,当然还有其他方式(如果它们存在!)来做.

Hab*_*bib 5

这适用于来自某个界面的方法.这是一个实现细节.如果一个类实现了某个接口,则接口的方法被标记为virtual,即多态行为(虚拟表中的位置(v表)).但它也标记为final (密封),以便实现基类的其他子类不能覆盖该特定方法.

请考虑以下示例:

interface SomeInterface
{
    void SomeMethod();
}

class SomeClass : SomeInterface
{
    public void SomeMethod() //This will be marked as virtual final in IL
    {
        //anything
    }
}
Run Code Online (Sandbox Code Playgroud)

来自:CLR通过C#(杰弗里里希特)

C#编译器要求将实现接口方法签名的方法标记为public.CLR要求将接口方法标记为虚拟.如果未在源代码中将该方法明确标记为虚拟,则编译器会将该方法标记为虚拟并密封; 这可以防止派生类重写接口方法.如果将方法明确标记为虚拟,则编译器将该方法标记为虚拟(并使其保持未密封状态); 这允许派生类重写接口方法.


Jon*_*nna 4

在这种情况下最终意味着什么?我的猜测是,这意味着“此方法无法被重写”,因此即使此方法在虚拟表中有一个槽,该槽也不能被派生类中的方法覆盖。

是的。它本身相当于 C# 关键字,sealed例如:

public override sealed string ToString()
{
  return "";
}
Run Code Online (Sandbox Code Playgroud)

变成:

.method public final hidebysig virtual instance string ToString () cil managed 
{
  ldstr ""
  ret
}
Run Code Online (Sandbox Code Playgroud)

virtual与 C# 关键字相关virtual,也与任何其他虚拟方法相关。这包括override任何接口实现。特别是,虽然您无法像virtual sealed在 C# 中那样定义方法(因为这毫无意义,因此您应该决定您想要做什么),但您可以在 CIL 中定义方法,并且这是在某些接口实现中完成的。

从 C# 的角度来看,可以通过三种方式实现接口方法或属性:

  1. 非虚拟。
  2. 虚拟(因此派生类可以覆盖它)。
  3. 明确实施。

从 CIL 的角度来看,所有这些都是virtual因为它们都使用虚拟方法机制。第一第三也final

(考虑到我们可以忽略覆盖机制并call在 CIL 中使用它来调用类中定义的方法,但我们必须callvirt与接口一起使用,因为我们不知道我们正在调用哪个类,所以有必须是查找)。

它只是由编译器(例如,如果您尝试重写密封方法,它将因编译错误而失败)或由 CLR(例如,当您尝试重写方法时,CLR 抛出异常)强制执行,还是由两者强制执行?

两个都。

C# 编译器不允许您重写密封方法:

public class Test
{
  public override sealed string ToString()
  {
    return "a";
  }
}

public class Test1 : Test
{
  public override string ToString()
  {
    return "";
  }
}
Run Code Online (Sandbox Code Playgroud)

这拒绝编译,并出现编译器错误 CS0239:“'Test1.ToString()':无法覆盖继承成员 'Test.ToString()',因为它是密封的”。

但如果你自己编写 CIL 来强制执行:

.class public auto ansi beforefieldinit Test extends [mscorlib]System.Object
{
  .method public hidebysig specialname rtspecialname instance void .ctor () cil managed 
  {
    ldarg.0
    call instance void [mscorlib]System.Object::.ctor()
    ret
  }

  .method public final hidebysig virtual instance string ToString () cil managed 
  {
    ldstr "a"
    ret
  }
}

.class public auto ansi beforefieldinit Test2 extends Mnemosyne.Test
{
  .method public hidebysig specialname rtspecialname instance void .ctor () cil managed 
  {
    IL_0000: ldarg.0
    IL_0001: call instance void Mnemosyne.Test::.ctor()
    IL_0006: ret
  }

  .method public hidebysig virtual instance string ToString () cil managed 
  {
    ldstr ""
    ret
  }
}
Run Code Online (Sandbox Code Playgroud)

然后,如果您调用new Test().ToString()"a",它会按您的预期返回,但如果您调用,new Test1().ToString()则会出现运行时错误:

System.TypeLoadException : Declaration referenced in a method implementation cannot be a final method.  Type: 'TestAssembly.Test2'.  Assembly: 'TestAssembly, Version=1.0.0.1, Culture=neutral, PublicKeyToken=null'.
Run Code Online (Sandbox Code Playgroud)

事实上,这使得整个类成为可卸载类型;是new Test1()抛出的,而不是ToString().

是否有任何合法的方法来“删除”、规避这个最终方法,即编写一个重写此方法的派生类?

不会。TypeLoadException任何尝试重写final方法的类都会发生这种情况。您唯一的选择是编辑基类并再次编译它。