Still More Complex Example: Object Creation and Method Invocation


We’ve examined the assembly and looked at the types that it contains. Having this access is useful for development tools such as IntelliSense that provide a complete list of an object’s contents, but we still haven’t solved the problem of programs that require late-bound access to objects. How can we create and access objects when we know neither the type of the object nor the member of it that we want to use until run time? The sample program from the previous section continues to demonstrate this. When you click the Invoke button on the method dialog box, I pop up a dialog box that provides controls for entering the parameters required for each method and a button for calling it, as shown in Figure 11-7.

An example of dynamic object creation and method invocation starts here.

click to expand
Figure 11-7: Dialog box used for calling a method via reflection

When I call a method, I obviously need to know which parameters to pass it. I can easily discover these via the MethodInfo.GetParameters method. This method returns an array of ParameterInfo structures, each of which describes a parameter that the method wants. The sample program dynamically creates a text box for each parameter and a label containing the parameter’s name and type, the code for which is shown in Listing 11-4.

The parameters required by a method are described in an array of ParameterInfo structures.

Listing 11-4: Code for creating method parameter entry controls

start example
 Public Sub SetupForInvoke(ByVal Method As System.Reflection.MethodInfo) ’ Remember the MethodInfo we’ll call later ThisMethod = Method ’ Remember the list of ParameterInfo objects for this method parms = ThisMethod.GetParameters ’ For each parameter the method requires, ’ create an edit control and a label If (parms.Length <> 0) Then tbs = New TextBox(parms.Length - 1) {} Dim i As Integer For i = 0 To parms.GetUpperBound(0) tbs(i) = New TextBox tbs(i).Location = New Point(10, 10 + 15 * i) tbs(i).Size = New Size(200, 10) Dim l As New Label l.Text = parms(i).Name + " as " + _ parms(i).ParameterType.ToString l.Location = New Point(215, 10 + 15 * i) l.Size = New Size(200, 20) Me.Controls.Add(tbs(i)) Me.Controls.Add(l) Next End If End Sub
end example

When the user clicks the Call button, I need to create an instance of the specified class and call the selected method, passing the correct parameters. The code for this is shown in Listing 11-5. It’s easy to create an object when we know its type at programming time; we simply use the new operator. How can we get the same functionality with a class whose identity we don’t discover until run time? The system-provided System.Activator class exists for this specific purpose. When the user invokes a method in the sample program, I call the static method Activator.CreateInstance, passing the type that describes the object we want to create, in this case, the one describing the method that the user has selected from the tree control. The activator figures out the assembly in which the specified type resides, creates the object, and calls its constructor. This is what actually happens under the hood when I use the new operator anyway.

You can create an object of any type by using System.Activator.

Listing 11-5: Code for creating an object and invoking its method dynamically

start example
 Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As _ System.EventArgs) Handles Button1.Click ’ User clicked the button so let’s call the method ’ First we have to create an object of this class Dim ThisObj As Object ThisObj = Activator.CreateInstance(ThisMethod.DeclaringType) ’ Now set up the parameter array for calling it Dim TheseParms As Object() If parms.Length <> 0 Then ’ Create new array of the correct number of objects TheseParms = New Object(parms.Length - 1) {} ’ Convert the value that the user entered in the text box ’ into the type that it should be Dim i As Integer For i = 0 To TheseParms.GetUpperBound(0) Dim NewType As Type NewType = parms(i).ParameterType TheseParms(i) = Convert.ChangeType(tbs(i).Text, NewType) Next End If ’ Make the actual call and display the result Dim ret = ThisMethod.Invoke(ThisObj, TheseParms) Label2.Text = ret.ToString() End Sub
end example

start sidebar
Tips from the Trenches

Creating an object and calling its methods via reflection obviously requires you to write more code than the simple early-bound case, and it should come as no surprise to learn that it also requires more time to execute. My customers report that if they know ahead of time which sets of functionality might appear on an object, they prefer to design each set into a separate interface (for example, IDisposable) and write early-bound client code against it. They can then use the is operator or a cast at run time to check an object for the presence of the interface and make the calls only if the object actually does support the interface.

end sidebar

Now that I’ve created the object, I want to call a method on it. Each kind of member has its own dedicated accessor function on its descriptor object, such as PropertyInfo.Get and PropertyInfo.Set. In this case, I use the MethodInfo.Invoke method to call a method on the object I’ve created. If I don’t know until run time even which kind of member I want to access, the Type.InvokeMember method (which is not shown) allows me to dynamically specify the kind of member that I want to access. I need to pass MethodInfo.Invoke an array of parameters, which I allocate as an array of System.Object objects. The Convert.ChangeType method easily converts each parameter from the text strings that the text boxes contain into whatever type the ParameterInfo structure says that the method requires. The return value of MethodInfo.Invoke is the return value of the method.

Calling a method or setting or getting a property is easy with MemberInfo- derived objects.

You’ll notice that the test object I provide with the reflection sample contains both public and private methods. You’ll also see that the sample program can call both the public and private methods on this object. Doesn’t this external access to a private method violate every rule in the book? It depends what you think the purpose of having rules is. As Jesus said, “The Sabbath was made for man, not man for the Sabbath” (Mark 2:27). The rules exist to benefit me, not I them, and I follow them only as long as they do that. There are several cases in which an outside client might want to call a private method for reasons that enhance rather than detract from the ultimate goal of producing good software quickly—a debugging program, for example, that wants to allow a programmer to view properties marked as private, or the system garbage collector calling an object’s finalizer. So you can see that calling a private method via reflection is something that at least occasionally needs to happen for the world to unfold as it should. The only question is should the mechanisms for doing it be kept hidden from ordinary eyes, revealed only by moonlight in Druid circles to initiated acolytes deemed worthy, as was the Burgermaster memory segment (there I go dating myself again) in early versions of Windows? Or should they be made public so that any programmer can see and understand them, and discussions of when their use is appropriate and when it isn’t take place in the clear light of day, and the permission that allows or blocks them be openly controlled so that everyone knows when external access to private members can happen and when it can’t? The .NET team has taken the latter approach, and I agree with it wholeheartedly. As Martin Luther King said, “Darkness cannot drive out darkness; only light can do that.” As with other powerful, and therefore double- edged, techniques, such as accessing unmanaged code, accessing private members via reflection requires the Reflection permission, which the system administrator can grant or revoke to various code groups. Users who are members of the debuggers group, for example, are automatically granted this privilege. Without it, reflection can access only public members, and most debuggers (including the Visual Studio debugger) detect this situation and refuse to run. This seems to me about the right balance.

Reflection can provide access to public and private members.




Introducing Microsoft. NET
Introducing Microsoft .NET (Pro-Developer)
ISBN: 0735619182
EAN: 2147483647
Year: 2003
Pages: 110

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net