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 会引发方法隐藏策略的变化,请及时和博主交流或写在评论。谢谢~

浙公网安备 33010602011771号