An Abstract Specification for Visual Basic Late Binding


In an earlier post, I talk about the implementation details of late bound expressions. That discussion considers the interface between the VB compiler and Late Binder but lacks an overall discussion of late binding: what it is, and how it works. What I present here is an abstract specification for VB late binding.


 


To generalize an earlier statement: late binding is all about figuring out which members to use while the program runs. Once the compiler decides that a particular expression requires late bound evaluation, it changes the expression into a helper call into the Late Binder (located in Microsoft.VisualBasic.dll). It is then up to the Late Binder to evaluate the expression during run time. This raises two important questions: Which expressions become late bound? And what does the Late Binder do at run time to evaluate these expressions?


 


Late Bound Expressions


 


Only certain kinds of Visual Basic expressions can become late bound. The Visual Basic Language Specification offers a definition:


 






11.3 Late-Binding Expressions


When the target of a member access expression or index expression is of type Object, the processing of the expression may be deferred until run time. Deferring processing this way is called late binding. Late binding allows Object variables to be used in a typeless way, where all resolution of members is based on the actual run-time type of the value in the variable. If strict semantics are specified by the compilation environment or by Option Strict, late binding causes a compile-time error. Non-public members are ignored when doing late-binding, including for the purposes of overload resolution. Note that, unlike the early-bound case, invoking or accessing a Shared member late bound will cause the invocation target to be evaluated at run time.



Note   If the expression is an invocation expression for a member defined on System.Object, late binding will not take place.


 


source: Microsoftâ Visual Basicâ .NET Language Specification, Version 7.1, p. 153


 


From the spec’s definition, when the target of a member access expression or index expression is of type Object, the expression may become late bound. In other words, using dot “.” or parentheses “()” on Object causes late binding (with one exception: using “.” to access a member defined on System.Object remains early bound). In fact, there are five distinct categories of late bound expressions:


 






















Late Bound Category


Purpose


Late Call


calling a method


Late Index Get


fetching a value using an index


Late Get


fetching the value of a member


Late Index Set


setting a value using an index


Late Set


setting the value of a member


 


Each category represents a usage of either “.” or “( )” on Object, and each has a distinct evaluation behavior. The earlier post addressed one of these categories, Late Call, but just scratches the surface as the following scenarios demonstrate:


 


    Sub Run(o As Object, …)


        ‘Late Call


        Call o.x()  ‘Scenario 1: Note the Call keyword is optional


        Call o.x(a) ‘Scenario 2: arguments may be supplied


 


        ‘Late Index Get


        v = o(a)    ‘Scenario 3


 


        ‘Late Get


        v = o.x     ‘Scenario 4


        v = o.x(a)  ‘Scenario 5: arguments may be supplied


 


        ‘Late Index Set


        o(a) = v    ‘Scenario 6


 


        ‘Late Set


        o.x = v     ‘Scenario 7


        o.x(a) = v  ‘Scenario 8: arguments may be supplied


    End Sub


 


Evaluation of Late Bound Expressions


 


Now that we know what kinds of expressions become late bound, we can discuss how they get evaluated at run time. The evaluation of any expression is a two step process: first decide what operation to do and then perform the operation. In the early binding model, the VB compiler makes the decisions and .NET performs the IL-encoded operations. In the late binding model both steps occur at run time, with the Late Binder making the decisions and .NET Reflection performing the operations. The decision is made by applying VB language rules to the expression. These rules depend on the category of the late bound expression and include member lookup, method overload resolution, shadowing and overriding semantics, etc. Once the rules have been applied and the decision made, the resulting operation is performed using Reflection. As currently designed, there are five types of Reflection operations used by the Late Binder:


 


System.Reflection.MethodInfo.Invoke — perform a function call


System.Array.GetValue — fetch a value from an array


System.Array.SetValue — store a value into an array


System.Reflection.FieldInfo.GetValue — fetch the value of a field


System.Reflection.FieldInfo.SetValue — store a value into a field


 


All late bound expressions eventually turn into a sequence of one or more of these Reflection operations. For example, consider a Late Get expression accessing a member “Position”:


 


    Sub Main()


        Dim o As Object = …


        Console.WriteLine(o.Position())


    End Sub


 


Because the expression belongs to the Late Get category, the Late Binder knows that “Position” must refer to a method, a property or a field. Say an object of type Knight is contained in o. There are at least three declarations of the type Knight that could make the expression o.Position() valid:


 


    ‘First possibility


    Class Knight


        Public Position As Integer


    End Class


 


    ‘Second possibility


    Class Knight


        Public Function Position() As Integer


        End Function


    End Class


 


    ‘Third possibility


    Class Knight


        Public ReadOnly Property Position() As Integer


            Get


            End Get


        End Property


    End Class


 


Also, Position cannot refer to a nested type or a method with parameters (since no arguments have been supplied at the call site). These declarations of type Knight would make the expression o.Position() not valid:


 


    ‘Fourth possibility


    Class Knight


        Structure Position


            Public Value As Integer


        End Structure


    End Class


 


    ‘Fifth possibility


    Class Knight


        Public Sub Position(ByRef Value As Integer)


        End Sub


    End Class


 


Upon deciding that Position is a valid member for a Late Get expression, the Late Binder invokes the appropriate Reflection operation. If Position refers to a field, then FieldInfo.GetValue will be called. If it refers to a method, MethodInfo.Invoke will be called instead. If it is not valid, as would be the case for a nested type, an exception is thrown.


 


An Abstract Description of Late Bound Expression Evaluation


 


It’s useful to consider that each late bound category is implemented as a unique helper function in the Late Binder. From here, I will explain the rules and operations used by each of these helper functions. Included below are several tables, one per late bound category. In the left column are the VB late binding scenarios indicated above. In the right column are the operations that result after applying the language rules. This column can also include late binding scenarios. Their inclusion means that the resulting operations are identical to that of the scenario indicated.


 


The tables make use of the following definitions:


 


      o is an expression typed as Object.


      x is an identifier


      a is an argument list


      v is an expression, either l-value or r-value


      d is a default property


 


Also, I will use the following shorthand to represent the Reflection operations:


 


      INVOKE o.x(a)


            call MethodInfo.Invoke(o, a) using the MethodInfo object for method x.


      ARRAYGET o[a]


            call CType(o, System.Array).GetValue(a)


      ARRAYSET o[a] = v


            call CType(o, System.Array).SetValue(v, a)


      FIELDGET o.x


            call FieldInfo.GetValue(o) using the FieldInfo for field x.


      FIELDSET o.x = v


            call FieldInfo.SetValue(o, v) using the FieldInfo for field x.


 


Finally, I give a very rough pseudo-code implementation of each helper function. The pseudo-code makes use of a function called PerformMemberResolution(). Consider this the member lookup and resolution process that applies shadowing, overloading, overriding and other various language semantics as described in-depth in the Visual Basic Language Specification. It is dependent upon the instance o, the member name “x” and the supplied arguments a (if any).




 


Late Call


 


A simple method call.


 
























Late Bound Scenario


Possible Operations


Comments


Call o.x()  ‘Scenario 1


INVOKE o.x()


when x is a method


 


INVOKE o.get_x()


when x is a property


Call o.x(a) ‘Scenario 2


INVOKE o.x(a)


when x is a method


 


INVOKE o.get_x(a)


when x is a property


 


If x is a sub or function then invoke x. If x is a property, invoke the get_x accessor function. The call may or may not have arguments.


 


Helper function pseudo-code:


 


    Function LateCall(Instance|o, MemberName|”x”, Arguments|a)


 


        Dim Result As Reflection.MemberInfo = _


            PerformMemberResolution(Instance, MemberName, Arguments)


 


        If IsMethod(Result) Then


            Return CType(Result, MethodInfo).Invoke(Instance, Arguments)


 


        ElseIf IsProperty(Result) Then


            Return CType(Result, PropertyInfo).GetGetMethod.Invoke(Instance, Arguments)


 


        End If


 


        Throw New Exception(“Unable to resolve late call expression”)


    End Function




 


Late Index Get


 


Fetching the value from an Object like an array.


 
















VB Scenario


Possible Operations


Comments


v = o(a) ‘Scenario 3


ARRAYGET o[a]


where o is an array


 


INVOKE o.get_d(a)


where o is a type with a default property


 


This scenario indexes into o to load a value. This scenario works if the object in o is either an array or a type with a default property. If o is an array, then use the supplied indices a to load the value. If o is a type with a default property d, invoke the get_d accessor function.


 


Note: This scenario requires arguments. If no arguments are supplied, this would be the simple assignment v = o (not a late bound expression).


 


Helper function pseudo-code:


 


    Function LateIndexGet(Instance|o, Arguments|a)


 


        If IsArray(Instance) Then


            Return CType(Instance, System.Array).GetValue(Arguments)


 


        ElseIf HasDefaultProperty(Instance) Then


            GetDefaultProperty(Instance).GetGetMethod.Invoke(Instance, Arguments)


 


        End If


 


        Throw New Exception(“Unable to resolve late index get expression”)


    End Function




 


Late Get


 


Doing member access on an Object to fetch a value.


 




























VB Scenario


Possible Operations


Comments


v = o.x    ‘Scenario 4


FIELDGET o.x


where x is a field


 


call o.x()  ‘Scenario 1


 


v = o.x(a) ‘Scenario 5


w(a)        ‘Scenario 3


w := FIELDGET o.x


 


w(a)        ‘Scenario 3


w := o.x  ‘Scenario 4


 


call o.x(a) ‘Scenario 2


 


 


This looks like Late Call but the requirement for a resulting value changes the kinds of members to which “x” can refer. Note that this is also different from Late Index Get since we’re using the member access operator “.” instead of the indexing operator “( )“.


 


With no arguments supplied, x can be either a field or a method. When x is a method, the resulting operation is the Late Call expression call o.x() (look up Scenario 1 in the Late Call table to see the possible operations).


 


With arguments supplied, x can be a method but also a field under special circumstances. When x is a field, fetch the value of field x and then use that in a new Late Index Get expression w(a). In other words, do a Late Index Get into the result of a Late Get. Similarly, if x is a method with no parameters, do a Late Call on that method to get the result w and then use it in Late Index Get w(a). Here’s an example with the two steps explicitly expressed:


                                                                                                                                                    


    ‘First possibility


    Class Knight


        Public Moves As Integer()


    End Class


 


    ‘Second possibility


    Class Knight


        Public Function Moves() As Integer()


        End Function


    End Class


 


    Sub Main()


        Dim o As Object = New Knight


        Dim w As Object = o.Moves   ‘First late bound expression


        Dim v As Integer = w(10)    ‘Second late bound expression where w contains an array


    End Sub


 


Lastly, if x is a method that takes arguments, then treat it as a Late Call.


 


Helper function pseudo-code:


 


    Function LateGet(Instance|o, MemberName|”x”, Arguments|a)


 


        Dim Result As Reflection.MemberInfo = _


            PerformMemberResolution(Instance, MemberName, Arguments)


 


        If Arguments.Count = 0 Then


 


            If IsField(Result) Then


                Return CType(Result, FieldInfo).GetValue(Instance)


 


            ElseIf IsMethod(Result) Then


                Return LateCall(Instance, MemberName, NoArguments)


 


            End If


 


        Else


            If IsField(Result) Then


                Dim w As Object = CType(Result, FieldInfo).GetValue(Instance)


                Return LateIndexGet(w, Arguments)


 


            ElseIf IsParameterlessMethod(Result) Then


                Dim w As Object = CType(Result, MethodInfo).Invoke(Instance, NoArguments)


                Return LateIndexGet(w, Arguments)


 


            ElseIf IsMethod(Result) Then


                Return LateCall(Instance, MemberName, Arguments)


 


            End If


        End If


 


        Throw New Exception(“Unable to resolve late get expression”)


    End Function




 


Late Index Set


 


Storing a value into an Object like an array.


 
















VB Scenario


Possible Operations


Comments


o(a) = v ‘Scenario 6


ARRAYSET o[a] = v


where o is an array


 


INVOKE o.set_d(v)


where o is a type with a default property


 


This scenario indexes into o to store a value. This scenario works if the object in o is either an array or a type with a default property. If o is an array, then use the supplied indices a to store the value. If o is a type with a default property d, invoke the set_d accessor function.


 


Note: This scenario requires arguments. If no arguments are supplied, this would be the simple assignment o = v (not a late bound expression).


 


Helper function pseudo-code:


 


    Sub LateIndexSet(Instance|o, Arguments|a, Value|v)


 


        If IsArray(Instance) Then


            Instance.SetValue(Value, Arguments)


            Return


 


        ElseIf HasDefaultProperty(Instance) Then


            GetDefaultProperty(Instance).GetSetMethod.Invoke(Instance, Arguments + Value)


            Return


 


        End If


 


        Throw New Exception(“Unable to resolve late index set expression”)


    End Sub




 


Late Set


 


Doing member access on an Object to set a value.


 




























VB Scenario


Possible Operations


Comments


o.x = v    ‘Scenario 7


FIELDSET o.x = v


where x is a field


 


INVOKE o.set_x(v)


 


o.x(a) = v ‘Scenario 8


w(a)      ‘Scenario 3


w := FIELDGET o.x


 


INVOKE o.set_x(a, v)


 


 


w(a) = v  ‘Scenario 3


w := o.x  ‘Scenario 4


 


Again, the existence of arguments changes the situation. The member x can either be a field, method, or property. If x is a field and no arguments are supplied, perform a field set. If arguments were supplied, fetch the value w in field x and then do a Late Index Set w(a).


 


If x is a property, this is a simple invocation of the set_x accessor. If x is a parameterless method, then invoke x to get the resulting value w and perform a Late Index Set w(a).


 


Helper function pseudo-code:


 


    Sub LateSet(Instance|o, MemberName|”x”, Arguments|a, Value|v)


 


        Dim Result As Reflection.MemberInfo = _


            PerformMemberResolution(Instance, MemberName, Arguments)


 


        If Arguments.Count = 0 Then


 


            If IsField(Result) Then


                CType(Result, FieldInfo).SetValue(Instance, Value)


                Return


 


            ElseIf IsProperty(Result) Then


                CType(Result, PropertyInfo).GetSetMethod.Invoke(Instance, Value)


                Return


 


            End If


 


        Else


            If IsField(Result) Then


                Dim w As Object = CType(Result, FieldInfo).GetValue(Instance)


                LateIndexSet(w, Arguments, Value)


                Return


 


            ElseIf IsProperty(Result) Then


                CType(Result, PropertyInfo).GetSetMethod.Invoke(Instance, Arguments + Value)


                Return


 


            ElseIf IsParameterlessMethod(Result) Then


                Dim w As Object = CType(Result, MethodInfo).Invoke(Instance, NoArguments)


                LateIndexSet(w, Arguments, Value)


                Return


 


            End If


        End If


 


        Throw New Exception(“Unable to resolve late set expression”)


    End Sub




 


In Closing


 


We’ve seen that only some expressions become late bound, and that these expressions are grouped into five major categories. Each category uses a unique set of VB language rules to decide the expression’s validity, and that after this decision process is made, a series of Reflection operations are used to perform the expression. Each category can be thought of as a helper function with specific behavior, and these behaviors have been described as a set of scenarios and resulting operations.


 


A final note: the Visual Basic runtime contains a function CallByName. The CallByName function performs a late bound evaluation on the given object, member name, and arguments. There is also an enum parameter UseCallType of type Microsoft.VisualBasic.CallType. Essentially, this enum parameter allows the programmer to choose the late binding category. CallType.Get, CallType.Set, and CallType.Method correspond to Late Get, Late Set, and Late Call respectively (a fourth value, CallType.Let, is used only for COM interop). In fact, the CallByName function is simply a Select Case statement that uses the CallType argument to choose the appropriate late binding helper function.

Comments (2)

  1. Thank you for some very useful information.

  2. 仪表 says:

    Real!It’s a very useful article.thx^_^,C# can also use it.