C#如何解析界面"虚拟"目标?

时间:2018-03-11 18:16:12

标签: c# c++ interface vtable

我要说C++,我有这个伪接口(仅abstract class只有pure virtual methods):

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 learnedcloseLog,使用动态类型IErrorLog,称为vtable FileErrorLog的{​​{1}}并选择所需的目标函数( 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的指针。

你能解释一下吗?

3 个答案:

答案 0 :(得分:3)

  

C#将如何解析closeLog()?

运行时将使用实现定义的机制正确解析调用。

在接口的情况下,这种机制非常复杂,并且有一些有趣的性能影响。

  

这是相同的机制吗?

它是否与某些C ++编译器用于不同类型系统的机制相同?几乎可以肯定没有。

然而,这些机制类似,因为有对象实例指针挂起的函数指针表,并且在运行时进行查找以进行方法调度。

  

因此,我不认为log是指向FileErrorLog的指针。

我认为你的意思是&#34;管理指针&#34 ;;在C#中,我们希望您将引用表征为&#34;引用&#34 ;;非托管指针是非常不同的。

由于 false 托管指针不同,因此您有错误的信念。你从这种错误的信念中得出的任何结论都是不正确推理的结果,并且不可靠。

根据您的问题和一些评论,您的核心错误信念似乎是对象的引用的内存中表示取决于用于存储引用的变量的类型。 这种信念完全是错误的,所以现在就不要再相信了。在CLR中,参考转化是表示,保留转化

如果对类C的对象的引用由数字0x12345678表示,然后将其转换为对C实现的接口I的引用,则表示仍为0x12345678。

  

任何类似于学习cpp的学习资源的教程?

此网站不适用于推荐教程。

答案 1 :(得分:1)

在C#(以及在CLI上运行的所有语言)中,引用是对象实例 - 而不是引用继承类型层次结构中的特定vtable或级别。编译时键入专门用于缩短方法的名称。在示例类型层次结构中:

{=INDEX($B$1:$F$1,MAX(IF($B2:$F2="dev",COLUMN($B2:$F2)-COLUMN($B2)+1)))} 
&#34;完整&#34;发出interface IFoo { void Bar(); } class CFoo : IFoo { public virtual void Bar() { } void IFoo.Bar() { } } class CFoo2 : CFoo { public override void Bar() { } } 的{​​{1}}操作码时使用的名称为callvirt。编译器只使用&#34; RValue&#34;的类型。以免你输入它。

在编译时没有进行强制转换或其他转换来调用继承的方法。无论c#中指定的类型如何,引用的值都保持不变。

考虑C#中的以下调用及其等效的IL编码:

var foo = new Foo(); foo.Bar()

从命名方法(例如:CFoo::Barprivate 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 } 的实现方法)的实际转换是在运行时或JIT时执行的 - 而不是在编译时。在较旧的运行时中,接口的IFoo::Bar指令将被命名为:

CFoo2::Bar

与C ++的不同之处在于,选择接口vtable的取消引用仅在JIT时完成,仅用于调用接口方法。

答案 2 :(得分:0)

您的代码基本上与以下内容相同:

    FileErrorLog tmp = new FileErrorLog();
    IErrorLog log = tmp;
    log.closeLog();

您只需从FileErrorLogIErrorLog隐式引用转化。引用的对象在两种情况下完全相同; c#中的引用转换始终是身份保留。

然后将对接口成员IErrorLog.closeLog的调用视为虚拟呼叫; IIRC接口成员是虚拟的“最终”成员。