首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >C#如何解析接口“虚拟”目标?

C#如何解析接口“虚拟”目标?
EN

Stack Overflow用户
提问于 2018-03-12 02:16:12
回答 3查看 509关注 0票数 1

假设我在C++中,我有这个伪接口(一个只有pure virtual methodsabstract class ):

代码语言:javascript
复制
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,使用动态类型的IErrorLogcloseLog被称为去虚拟化FileErrorLogvtable并选择所需的目标函数(closeLog()指针)。

因为在C#interfaces中并不是真正的类,所以当我这样做的时候:

代码语言:javascript
复制
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的指针。

你能给我解释一下吗?

EN

回答 3

Stack Overflow用户

发布于 2018-03-12 06:43:01

C#将如何解析该closeLog()?

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

在接口的情况下,这种机制相当复杂,并且存在有趣的性能影响。

它是相同的机制吗?

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

然而,这些机制是相似的,因为有挂在对象实例指针上的函数指针表,并且在运行时进行查找以进行方法分派。

因此,我不认为

是指向FileErrorLog的指针。

我假设您的意思是“托管指针”;在C#中,我们更希望您将引用描述为“引用”;非托管指针则非常不同。

由于托管指针不同是错误的,因此您有一个错误的信念。你从错误的信念中得出的任何结论都是不合理的推理的结果,也是不可靠的。

根据您的问题和许多评论,您的核心错误信念似乎是,对对象的引用在内存中的表示形式取决于用于存储引用的变量的类型。这个信念是100%完全错误的,所以现在就停止相信它吧,。在CLR中,引用转换是保留表示的转换。

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

有没有类似于learncpp源码的教程来理解C#行为?

这个网站不是用来推荐教程的。

票数 4
EN

Stack Overflow用户

发布于 2018-03-12 05:30:15

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

代码语言:javascript
复制
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编码:

代码语言:javascript
复制
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::BarCFoo2::Bar的实现方法)的实际转换是在运行时或即时执行时执行的,而不是在编译时执行。在较早的运行时中,接口的callvirt指令将被编译为:

代码语言:javascript
复制
; 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的解引用仅在即时执行时完成,并且仅用于对接口方法的调用。

票数 1
EN

Stack Overflow用户

发布于 2018-03-12 03:23:31

您的代码本质上与以下代码相同:

代码语言:javascript
复制
    FileErrorLog tmp = new FileErrorLog();
    IErrorLog log = tmp;
    log.closeLog();

您只需执行从FileErrorLogIErrorLog的隐式引用转换。被引用的对象在这两种情况下是完全相同的;c#中的引用转换总是保持身份不变。

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

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/49223430

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档