假设我在C++中,我有这个伪接口(一个只有pure virtual methods的abstract class ):
class IErrorLog
{
public:
virtual bool closeLog() = 0;
};
class FileErrorLog : public IErrorLog
{
public:
FileErrorLog(){}
~FileErrorLog(){}
bool closeLog() {
std::cout << "Close FileErrorLog" << std::endl;
return true;
}
};
int main()
{
FileErrorLog fileErrorLog;
IErrorLog *log = &fileErrorLog;
log->closeLog();
}对于I've learned,使用动态类型的IErrorLog的closeLog被称为去虚拟化FileErrorLog的vtable并选择所需的目标函数(closeLog()指针)。
因为在C#的interfaces中并不是真正的类,所以当我这样做的时候:
interface IErrorLog {
void closeLog ();
}
public class FileErrorLog : IErrorLog
{
public FileErrorLog() {}
public void closeLog() {
Console.WriteLine("Close FileErrorLog");
}
}
public class Program
{
public static void Main(string[] args)
{
IErrorLog log = new FileErrorLog();
log.closeLog();
}
}C#将如何解析该closeLog()?这是相同的机制吗?
因为在这里IErrorLog log不再是abstract class了。它是一个本机类型。因此,我不认为log是指向FileErrorLog的指针。
你能给我解释一下吗?
发布于 2018-03-12 06:43:01
C#将如何解析该closeLog()?
运行库将使用实现定义的机制正确地解析调用。
在接口的情况下,这种机制相当复杂,并且存在有趣的性能影响。
它是相同的机制吗?
它的机制是否与某些C++编译器用于不同类型系统的机制相同?几乎可以肯定不会。
然而,这些机制是相似的,因为有挂在对象实例指针上的函数指针表,并且在运行时进行查找以进行方法分派。
因此,我不认为
是指向FileErrorLog的指针。
我假设您的意思是“托管指针”;在C#中,我们更希望您将引用描述为“引用”;非托管指针则非常不同。
由于托管指针不同是错误的,因此您有一个错误的信念。你从错误的信念中得出的任何结论都是不合理的推理的结果,也是不可靠的。
根据您的问题和许多评论,您的核心错误信念似乎是,对对象的引用在内存中的表示形式取决于用于存储引用的变量的类型。这个信念是100%完全错误的,所以现在就停止相信它吧,。在CLR中,引用转换是保留表示的转换。
如果对类类型C的对象的引用由数字0x12345678表示,然后将其转换为对由C实现的接口I的引用,则表示仍然是0x12345678。
有没有类似于learncpp源码的教程来理解C#行为?
这个网站不是用来推荐教程的。
发布于 2018-03-12 05:30:15
在C# (以及在命令行界面上运行的所有语言)中,引用是对对象实例的引用,而不是对继承类型层次结构中的特定vtable或级别的引用。编译时类型专门用于缩短方法的名称。在示例类型层次结构中:
interface IFoo
{
void Bar();
}
class CFoo : IFoo
{
public virtual void Bar()
{
}
void IFoo.Bar()
{
}
}
class CFoo2 : CFoo
{
public override void Bar()
{
}
}发出var foo = new Foo(); foo.Bar()的callvirt操作码时使用的“完整”名称是CFoo::Bar。编译器只是使用"RValue“的类型来省去你输入它的麻烦。
不需要在编译时进行强制转换或其他转换来调用继承的方法。无论在c#中指定哪种类型,引用的值都保持不变。
考虑C#中的以下调用,以及它们的等效IL编码:
private static void CallFooBar()
{
// L_0000: newobj instance void InterfaceDemo.CFoo::.ctor()
CFoo foo = new CFoo();
// Note that the next call (since the variable was typed CFoo) is not calling
// the interface implementation.
// VVVV
// L_0005: callvirt instance void InterfaceDemo.CFoo::Bar()
foo.Bar();
// L_000a: ret
}
private static void CallFooIFooBar()
{
// Note that the type cast does not affect the value reference on the
// stack (no cast is performed). The instantiation looks identical to
// CallFooBar above.
//
// L_0000: newobj instance void InterfaceDemo.CFoo::.ctor()
CFoo foo = new CFoo();
IFoo ifoo = foo;
// Note that the call is made to the interface method (to be dispatched
// through the interface method tables)
// VVVV
// L_0005: callvirt instance void InterfaceDemo.IFoo::Bar()
ifoo.Bar();
// L_000a: ret
}
private static void CallFooIFooBar2()
{
// Note that all of the compiled IL is identical to CallFooIFooBar
//
// L_0000: newobj instance void InterfaceDemo.CFoo::.ctor()
IFoo foo = new CFoo();
// L_0005: callvirt instance void InterfaceDemo.IFoo::Bar()
foo.Bar();
// L_000a: ret
}
private static void CallCFoo2Bar()
{
// Note that all of the IL excepting for the newobj call is identical.
// virtual method resolution takes place at runtime (or at JIT) - not
// at compile time.
//
// L_0000: newobj instance void InterfaceDemo.CFoo2::.ctor()
IFoo foo = new CFoo2();
// L_0005: callvirt instance void InterfaceDemo.IFoo::Bar()
foo.Bar();
// L_000a: ret
}从命名方法(例如:IFoo::Bar到CFoo2::Bar的实现方法)的实际转换是在运行时或即时执行时执行的,而不是在编译时执行。在较早的运行时中,接口的callvirt指令将被编译为:
; C#: ((IFoo)foo).Bar();
; C: (*(foo->TypeHandle->InterfaceMap[0x30]))(foo)
mov ecx,edi ; move "foo" pointer into ecx
mov eax,dword ptr [ecx] ; Dereference to place MethodTable into eax
mov eax,dword ptr [eax+0Ch] ; Dereference to interface map address
; (offset 12 is constant for that version of
; the CLR)
mov eax,dword ptr [eax+30h] ; move the ifc impl start slot into eax
; (30h is discovered at time of JIT by
; examining the loaded type hierarchy)
call dword ptr [eax] ; call foo.Bar这里与C++的不同之处在于,选择接口vtable的解引用仅在即时执行时完成,并且仅用于对接口方法的调用。
发布于 2018-03-12 03:23:31
您的代码本质上与以下代码相同:
FileErrorLog tmp = new FileErrorLog();
IErrorLog log = tmp;
log.closeLog();您只需执行从FileErrorLog到IErrorLog的隐式引用转换。被引用的对象在这两种情况下是完全相同的;c#中的引用转换总是保持身份不变。
然后,对接口成员IErrorLog.closeLog的调用被视为虚拟调用;IIRC接口成员是虚拟的“最终”成员。
https://stackoverflow.com/questions/49223430
复制相似问题