IL分析之C#父子类同名方法的隐藏策略

一. 测试要点

如何理解下面这句话?

  从本质上来说,当父子类存在同名的方法时,有两种隐藏策略

    1. 按名称(hide by name)

    2. 按签名(hide by signature)

  C# 使用后者,也就是说,只有方法名和输入参数完全相同时,才构成隐藏。我们无法通过 C# 代码来指定 CLR 使用哪种方式,但可以在 IL 中进行调整(通过在子类的的方法添加或删除 hidebysig 元数据特性)。

这是在 《 C# 从现象到本质 》第四章 C# 和面向对象 P99 上所写的。

  • 上面所说的 " 按名称 " 和 " 按签名 " 中的 " 名称 " 和 " 签名 " 指的是什么?
  • 如何通过 IL 中进行调整?

二. 理解 " 按名称 " 和 " 按签名 "

1. " 按名称 " 中的 " 名称 " 指的是,函数方法的名称

2. " 按签名 " 中的 " 签名 " 包含了如下3个部分:

  • 方法的名称
  • 返回值
  • 参数个数及其类型
  • 访问修饰符

 

※ 下面是来自网上的一个定义

方法签名:通过制定方法的访问级别(例如 public 或 private)、可选修饰符(例如 abstract 或 sealed)、返回值、名称和任何方法参数,可以在类或结构中声明的方法,这些部分统称为方法的签名。

 

※ hidebysig 的作用是什么?

网上解释:hidebysig is supplied for the use of tools and is ignored by the VES. It specifies that the declared method hides all methods of the base class types that have a matching method signature; when omitted, the method should hide all methods of the same name, regardless of the signature.

翻译:hidebysig是提供给工具使用的,而VES忽略了它。它指定声明的方法隐藏基类类型中具有匹配方法签名的所有方法;当省略时,该方法应该隐藏所有相同名称的方法,而不考虑签名。 

 

※ 要注意的是:

1. 在 C# 中进行方法重载,方法的返回类型修饰符不是方法签名的一部分。而对于委托来说,委托所指的方法签名包含返回类型,但也不包含修饰符;

这边是一个方法重载的例子,如下代码:

public abstract class TestClass1
{
    public void ShowHelloWorld()
    {
        Console.WriteLine("Hello World!");
    }

    public string ShowHelloWorld()
    {
        Console.WriteLine("Hello World!");
        return string.Empty;
    }

    public abstract void ShowHelloWorld();
}

有如下的错误:

【1】不能声明一个返回值不同而函数名称和参数一样的函数

【2】不能声明一个修饰符不同而返回值相同、函数名称和参数一样的函数

2. 可变参数的方法会改变方法的签名,对于同时可以调用可变参数方法和普通方法的话,优先调用可变参数方法,测试代码如下:

class Program
{
    static void Main(string[] args)
    {
        TestDemo testDemo = new TestDemo();
        testDemo.ShowHelloWorld();

        Console.ReadKey();
    }
}

public class TestDemo
{
    public void ShowHelloWorld(params string[] contents)
    {
        Console.WriteLine("Params Hello World!");
    }

    public void ShowHelloWorld(string content)
    {
        Console.WriteLine("Normal Hello World!");
    }
}

运行结果:

三. 查看一下 C# 代码的 IL

C# 代码:

public class Father
{
    public void ShowHelloWorld()
    {
        Console.WriteLine("Father : Hello World!");
    }
}

public class Son : Father
{
    public new void ShowHelloWorld()
    {
        Console.WriteLine("Son : Hello World!");
    }
}

IL 代码为:

.method public hidebysig instance void  ShowHelloWorld() cil managed
{
  // Code size       13 (0xd)
  .maxstack  8
  IL_0000:  nop
  IL_0001:  ldstr      "Father : Hello World!"
  IL_0006:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_000b:  nop
  IL_000c:  ret
} // end of method Father::ShowHelloWorld

.method public hidebysig instance void  ShowHelloWorld() cil managed
{
  // Code size       13 (0xd)
  .maxstack  8
  IL_0000:  nop
  IL_0001:  ldstr      "Son : Hello World!"
  IL_0006:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_000b:  nop
  IL_000c:  ret
} // end of method Son::ShowHelloWorld

IL 的上端调用为:

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       21 (0x15)
  .maxstack  1
  .locals init ([0] class Demo.Father father)
  IL_0000:  nop
  IL_0001:  newobj     instance void Demo.Son::.ctor()
  IL_0006:  stloc.0
  IL_0007:  ldloc.0
  IL_0008:  callvirt   instance void Demo.Father::ShowHelloWorld()
  IL_000d:  nop
  IL_000e:  call       valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()
  IL_0013:  pop
  IL_0014:  ret
} // end of method Program::Main

可以看到这边 C# 代码转 MSIL 代码,在函数的前面都有 hidebysig。然后在 Main 函数中看到 father 对象是被指定为 Demo.Father 类型,然后用 Demo.Son 类型创建,最后在使用 "  callvirt instance void Demo.Father::ShowHelloWorld() " 来调用执行。如果我们把这个 hidebysig 去掉会发生什么?

四. 去除 hidebysig 的 IL 代码测试

父类方法中不是虚方法的 IL 测试:

.assembly extern mscorlib
{
    auto
}
.assembly test{}
.module test.exe

.class public Father
{
    .method public specialname void .ctor()
    {
        ldarg.0
        call instance void [mscorlib]System.Object::.ctor()
        ret
    }
    
    .method public void ShowHelloWorld()
    {
        ldstr "Father : Hello World!"
        call void [mscorlib]System.Console::WriteLine(string)
        ret
    }
}

.class public Son extends Father
{
    .method public specialname void .ctor()
    {
        ldarg.0
        call instance void Father::.ctor()
        ret
    }
    
    .method public void ShowHelloWorld()
    {
        ldstr "Son : Hello World!"
        call void [mscorlib]System.Console::WriteLine(string)
        ret
    }
}

.method public static void Main()
{
    .entrypoint
    .locals init ([0] class Father v_0)
    newobj instance void Son::.ctor()
    stloc.0
    ldloc.0
    callvirt instance void Father::ShowHelloWorld()
    call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()
    pop
    ret
}

父类方法是虚方法的 IL 代码:

.assembly extern mscorlib
{
    auto
}
.assembly test{}
.module test.exe

.class public Father
{
    .method public specialname void .ctor()
    {
        ldarg.0
        call instance void [mscorlib]System.Object::.ctor()
        ret
    }
    
    .method public virtual newslot void ShowHelloWorld()
    {
        ldstr "Father : Hello World!"
        call void [mscorlib]System.Console::WriteLine(string)
        ret
    }
}

.class public Son extends Father
{
    .method public specialname void .ctor()
    {
        ldarg.0
        call instance void Father::.ctor()
        ret
    }
    
    .method public void ShowHelloWorld()
    {
        ldstr "Son : Hello World!"
        call void [mscorlib]System.Console::WriteLine(string)
        ret
    }
}

.method public static void Main()
{
    .entrypoint
    .locals init ([0] class Father v_0)
    newobj instance void Son::.ctor()
    stloc.0
    ldloc.0
    callvirt instance void Father::ShowHelloWorld()
    call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()
    pop
    ret
}

运行结果为:

Father : Hello World!

五. 结果分析

书上说通过 IL 代码可以修改影藏行为,但是根据测试之后发现,测试的结果并没有和原来有 hidebysig 的时候没有什么区别。后来查阅 《NET探秘:MSIL权威指南》后看到:

 

说这个标志并没有被 IL 的编译器所使用,也就是说无论你有没有写 hidebysig 都会根据签名来隐藏。

六. 勘误

上面的分析,确实是和书中说的有差别,如果有什么错误,或者有什么例子可以证明去掉 hidebysig 会引发方法隐藏策略的变化,请及时和博主交流或写在评论。谢谢~

七. 测试工程代码

文章的工程代码下载

posted @ 2020-01-15 15:18  霁雪湖上三映月  阅读(617)  评论(0)    收藏  举报