如何在不装箱的前提下调用“显式实现”的接口方法?
2013-03-24 21:45 by 老赵, 4661 visits上篇文章谈了针对一个struct对象使用using
语句的时候是否会装箱的问题,结论是“不会”。尽管using
语句是针对IDisposable
接口的,但我们在调用的时候其实已经明确了目标方法,因此根本不需要装箱(或查找虚函数表)了。后来有同学在微博上提出,这点在《CLR via C#》这本书里提到过,于是我去翻了翻,原来是这样的:
internal struct Point : IComparable { private readonly Int32 m_x, m_y; // Constructor to easily initialize the fields public Point(Int32 x, Int32 y) { m_x = x; m_y = y; } // Implementation of type-safe CompareTo method public Int32 CompareTo(Point other) { // Use the Pythagorean Theorem to calculate // which point is farther from the origin (0, 0) return Math.Sign(Math.Sqrt(m_x * m_x + m_y * m_y)); } public Int32 CompareTo(Object o) { if (GetType() != o.GetType()) { throw new ArgumentException("o is not a Point"); } // Call type-safe CompareTo method return CompareTo((Point)o); } } public static class Program { public static void Main() { // Create two Point instances on the stack. Point p1 = new Point(10, 10); Point p2 = new Point(20, 20); // p1 does NOT get boxed to call CompareTo. // p2 does NOT get boxed because CompareTo(Point) is called. Console.WriteLine(p1.CompareTo(p2)); // "-1" // p1 DOES get boxed, and the reference is placed in c. IComparable c = p1; ... } }
我参考的是其第四版电子版,这段代码出现在第5章,133页开始。那位同学说,你看这里在调用CompareTo
方法的时候,书上写的很明白没有装箱嘛。我看了之后表示这跟前一篇谈的内容其实并没有太多联系。假如从IL上看,这次调用是这样的:
.method public hidebysig static void Main () cil managed { // Method begins at RVA 0x26b8 // Code size 36 (0x24) .maxstack 3 .locals init ( [0] valuetype Point p1, [1] valuetype Point p2 ) IL_0000: ldloca.s p1 IL_0002: ldc.i4.s 10 IL_0004: ldc.i4.s 10 IL_0006: call instance void Point::.ctor(int32, int32) IL_000b: ldloca.s p2 IL_000d: ldc.i4.s 20 IL_000f: ldc.i4.s 20 IL_0011: call instance void Point::.ctor(int32, int32) IL_0016: ldloca.s p1 IL_0018: ldloc.1 IL_0019: call instance int32 Point::CompareTo(valuetype Point) IL_001e: call void [mscorlib]System.Console::WriteLine(int32) IL_0023: ret } // end of method Program::Main
请注意IL_0019,它明确地指出此次call
指令的目标是Point
类型的CompareTo
,与IComparable
接口没有任何关系。我们可以从Point
定义中删除这个接口,这都不影响此次调用。而假如您返回上一篇文章,就会发现using
语句生成的IL会查找IDisposable
上的方法:
IL_0017: constrained. .DisposableStruct IL_001d: callvirt instance void [mscorlib]System.IDisposable::Dispose()
我们都知道,把一个struct对象赋值到IDisposable
引用上之后会产生装箱,这才是上一篇文章中“疑惑”的由来。假如我们只是要直接调用Dispose
方法,自然就不会想这么多了。我又粗略翻了翻《CLR via C#》相关内容,发现它没有提到过这一点,看来我也算弥补一个空白了,表示略为开心。
不过话又说回来,假如有一个类型,它是这么实现的:
public struct DisposableStruct : IDisposable { void IDisposable.Dispose() { } }
换句话说,这个struct并没有定义一个Dispose
方法,而是显式实现了某个接口方法(不仅限于IDisposable
接口)。在这个情况下,您有什么办法在C#里调用这个接口方法呢?当然,调用时不能在堆上产生任何对象。
不知道老师的题目是针对所有显式实现的接口,还是只针对 IDisposable 接口呢? 如果只针对 IDisposable 接口的话,用老师上一篇文章提到的 using 即可,显式实现与普通实现 IDisposable 的IL代码是一样的。 如果是针对所有显式实现接口的话就不懂了,求解答。