Почему поведение перегруженных функций-членов отличается от поведения функций, не являющихся членами

Я заметил, что параметры, передаваемые глобальным функциям, всегда используют «вызов», но вызовы перегруженных функций-членов всегда используют «вызов».

Почему он всегда вызывает перегруженную функцию, относящуюся к базовому классу, а не к производному классу, когда этот класс передается в глобальную функцию (Print())?

Похоже, он выбирает, какую перегруженную функцию Print запускать во время компиляции, есть ли что-то особенное, что я сделал, чтобы он решил, что это не должно решаться во время выполнения?

Вот некоторый код, который демонстрирует, что я имею в виду:

Module Module1

    Class BaseClass
        Friend Overridable Sub Print()
            Console.WriteLine("BaseClass.Print")
        End Sub
    End Class

    Class DerivedClass
        Inherits BaseClass

        Friend Overrides Sub Print()
            Console.WriteLine("DerivedClass.Print")
        End Sub
    End Class

    Sub Print(iObject As Object)
        Console.WriteLine("Object")
    End Sub

    Sub Print(iClass1 As BaseClass)
        Console.WriteLine("BaseClass")
    End Sub

    Sub Print(iClass2 As DerivedClass)
        Console.WriteLine("DerivedClass")
    End Sub

    Sub Main()
        Dim tBaseClass As New BaseClass
        Dim tDerivedClass As New DerivedClass
        Dim tBaseClassRef As BaseClass
        Dim tObjPtr As Object

        Console.WriteLine()
        Console.WriteLine("Test 1")
        Console.WriteLine()

        'in IL it always uses callvirt for an overloaded member function

        'from MSDN:     The callvirt instruction calls a late-bound method on an object. 
        '               That is, the method is chosen based on the runtime type of obj rather than the compile-time class visible in the method pointer.

        'prints "BaseClass.print"
        'callvirt   instance void Overloading.Module1/BaseClass::Print()
        tBaseClass.Print()

        'in IL it uses "call", even though Print() is overloaded? Why is this?

        'We slip by here because this type is not late bound

        'prints "BaseClass"
        'call       void Overloading.Module1::Print(class Overloading.Module1/BaseClass)
        Print(tBaseClass)




        Console.WriteLine()
        Console.WriteLine("Test 2")
        Console.WriteLine()

        'prints "DerivedClass.print"
        'callvirt   instance void Overloading.Module1/BaseClass::Print()
        tDerivedClass.Print()

        'call works out okay here too because we're still not late bound

        'prints "DerivedClass"
        'call       void Overloading.Module1::Print(class Overloading.Module1/BaseClass)
        Print(tDerivedClass)




        Console.WriteLine()
        Console.WriteLine("Test 3")
        Console.WriteLine()

        tBaseClassRef = tBaseClass



        tBaseClassRef.Print()
        'prints "BaseClass.print"
        'callvirt   instance void Overloading.Module1/DerivedClass::Print()

        'call took our word for it that tBaseClassRef is BaseClass typed
        'which is correct

        Print(tBaseClassRef)
        'prints "BaseClass"
        'call       void Overloading.Module1::Print(class Overloading.Module1/DerivedClass)



        Console.WriteLine()
        Console.WriteLine("Test 4")
        Console.WriteLine()


        tBaseClassRef = tDerivedClass



        tBaseClassRef.Print()
        'prints "DerivedClass.print"
        'IL_0098:  callvirt   instance void Overloading.Module1/BaseClass::Print()

        'Callvirt correctly handles our tBaseClass having a derived class's type


        Print(tBaseClassRef)
        'prints "BaseClass" <!>

        'IL_009f:  call       void Overloading.Module1::Print(class Overloading.Module1/BaseClass)

        '"Call" is ill-equipped to handle our Derived class

        Console.WriteLine()
        Console.WriteLine("Test 5")
        Console.WriteLine()

        tObjPtr = tDerivedClass

        tObjPtr.Print() 
            '(I don't expect this to work, but the error message surprised me)
            '   -- unhandled exception -- "Public member 'Print' on type 'DerivedClass' <??> not found.

        'IL instructions: http://en.wikipedia.org/wiki/List_of_CIL_instructions

        'a. where is it getting DerivedClass from for the exception? 
        'b. Why did LateCall know what type it was, but was unable to find the method?

        '  [ILDASM]
        '  IL_00be:  ldloc.3    //Load local [3] object tObjPtr)
        '  IL_00bf:  ldnull     // push NULL to stack (no string)
        '  IL_00c0:  ldstr      "Print" // push string object for literal string -- is this what we're using as our object?
        '  IL_00c5:  ldc.i4.0   //Push False
        '  IL_00c6:  newarr     [mscorlib]System.Object
        '  IL_00cb:  ldnull     // no string array
        '  IL_00cc:  ldnull     // no class array
        '  IL_00cd:  ldnull     // no bool array
        '  IL_00ce:  ldc.i4.1   //Push True
        '  IL_00cf:  call       object [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.NewLateBinding::LateCall(object,
        '                                                                                                                    class [mscorlib]System.Type,
        '                                                                                                                    string,
        '                                                                                                                    object[],
        '                                                                                                                    string[],
        '                                                                                                                    class [mscorlib]System.Type[],
        '                                                                                                                    bool[],
        '                                                                                                                    bool)

        Console.WriteLine("Marker") 'divider so I can figure out what it is in idasm

        Print(tObjPtr)                                                                                                           
        ' prints "object"

        'again, why use "call" instead of "callvirt"? There is a more specific definition of Print that can handle this
        '[ILDASM]
        '   IL_00e0:  ldloc.3
        '   IL_00e1:  call       object [mscorlib]System.Runtime.CompilerServices.RuntimeHelpers::GetObjectValue(object)
        '   IL_00e6:  call       void Overloading.Module1::Print(object)

    End Sub

End Module

РЕДАКТИРОВАТЬ: Также, пожалуйста, поделитесь своими источниками, я хотел бы узнать больше о том, как работает IL. Это использует .NET 4.0, если это имеет значение.


person jrh    schedule 04.06.2015    source источник
comment
Я нашел этот ответ намного позже , что объясняет, что компилятор предназначен для определения того, какую перегруженную версию Print() использовать. Решение о перегрузке принимается во время компиляции (помимо использования динамической типизации в C# 4) на основе типа аргументов во время компиляции. Обратите внимание, что вызов поздней привязки VB.NET к Print на tObjPtr завершается ошибкой, поскольку Print в DerivedClass равно Friend, а не Public.   -  person jrh    schedule 06.01.2017


Ответы (1)


Инструкция callvirt вызывает метод с поздней привязкой для объекта.

Это означает: на объекте экземпляра этот метод «принадлежит»; в случае метода модуля (общий метод, если это был класс) экземпляр не задействован -> нет необходимости в callvirt.

Здесь задействованы два понятия: перегрузка и переопределение (что является особой формой перегрузки); callvirt действительно нужен только для последнего.

Что касается выбора перегрузки печати, вы можете увидеть связанные документация

person Sehnsucht    schedule 04.06.2015
comment
Я хотел бы добавить раздел методов вызова экземпляра: en.wikipedia.org/wiki/Common_Intermediate_Language, одной тонкости, которую я упустил, является то, что callvirt предполагает, что имя/параметры метода не изменятся, но он позволяет изменять загружаемый в стек объект и его тип. - person jrh; 05.06.2015