Reflecting Members

Reflecting Members

Just as you are likely to reflect types and create instances, you will encounter scenarios where you want to explore other members of types. In this section I will demonstrate how to use Reflection to request other kinds of members.

Reflecting Methods

Type objects are used to interact with members of a type. You use Type.InvokeMember to invoke Reflected members whether those members are methods, properties, events, fields, or whatever. The key to invoking the member correctly is providing the correct information to the InvokeMember method, including the correct BindingFlags enumerated values. Listing 4.8 demonstrates how to invoke a protected method member.

Listing 4.8 Invoking a Protected Member Method by Using Reflection
 Dim Instance As ReflectMembers = New ReflectMembers() Dim T As Type = Instance.GetType() T.InvokeMember("ProtectedMethod", _   BindingFlags.InvokeMethod Or BindingFlags.NonPublic _   Or BindingFlags.Instance, _   Nothing, Instance, New Object() {}) 

The method we are invoking is defined in the ReflectMembers class in the ReflectMembers.sln example program. The first statement creates an instance of the ReflectMembers class. The second statement obtains a Type object for ReflectMembers . (The first two statements are identical for all the examples in the rest of this section; I won't repeat the descriptions again later.) Using the Type object we can call InvokeMember , invoking a method named ProtectedMethod .

The first argument of InvokeMember is the name of the member. (There are a couple of overloaded versions of InvokeMember . You can explore the variations in the help documentation.) The second parameter is the bitmasked BindingFlags . We use BindingFlags.InvokeMethod , BindingFlags.NonPublic , and BindingFlags.Instance strung together with Or to create the correct bitmask. InvokeMethod tells Reflection that we are looking for a method; NonPublic tells Reflection that we are looking for a nonpublic method; and Instance (as opposed to Static ) tells Reflection that we are referring to a nonshared member.

The third argument of InvokeMember in Listing 4.8 ”represented by Nothing ”is the Binder argument (refer to the Implementing a Simple Custom Binder subsection later in this section for an example of a binder). We aren't converting any data, so we don't need a binder.

The fourth argument is the object against which we want to invoke the member. Keep in mind that we can use late binding if Option Strict Off is set at the top of the module, and if we aren't loading the assembly using Reflection, we would invoke the member in the normal way. That is, we would simply create an object and call the method.

The final argument is the array of parameters we are passing to the method. ProtectedMethod has no arguments; thus we pass an empty array of objects to InvokeMember .

Reflecting Parameters

If you are reflecting types and methods because you don't know what is available in the assembly, you are likely not to know what a particular method's parameters are. You can use Reflection to obtain information about the order, type, and adornment of method parameters. The first step is to get the MethodInfo record for a particular method and then request the array of parameters. Listing 4.9 demonstrates how to obtain the parameters for a Reflected method.

Listing 4.9 Getting Parameter Information by Using Reflection
 1:  Private Sub ReflectParameter() 2:    Dim Instance As ReflectMembers = New ReflectMembers() 3:    Dim T As Type = Instance.GetType() 4:    Dim Method As MethodInfo = T.GetMethod("PublicFunction") 5: 6:    Dim Parameters() As ParameterInfo = _ 7:      Method.GetParameters() 8: 9:    Dim Parameter As ParameterInfo 10:   For Each Parameter In Parameters 11:     WriteParameter(Parameter) 12:   Next 13: End Sub 14: 15: Private Sub WriteParameter(ByVal P As ParameterInfo) 16:   Dim s As StringBuilder = New StringBuilder() 17: 18:   s.Append("Name=") 19:   s.Append(P.Name) 20:   s.Append(", Type=") 21:   s.Append(P.ParameterType().ToString()) 22:   s.Append(", IsOptional=") 23:   s.Append(P.IsOptional().ToString) 24:   s.Append(", Position=") 25:   s.Append(P.Position.ToString()) 26:   s.Append(", DefaultValue=") 27:   s.Append(P.DefaultValue.ToString()) 28:   Console.WriteLine(s.ToString()) 29: End Sub 

TIP

Strings are immutable in .NET. If you are going to perform multiple string concatenations, using a System.Text.StringBuilder object will be much faster than using a String object.

Listing 4.9 demonstrates how to request the MethodInfo object for PublicFunction in line 4. From the MethodInfo object named Method (line 7) we can request all the parameters for that method. Lines 9 through 12 iterate through all the ParameterInfo objects returned by GetParameters and writes some of the details of each parameter to the console. Knowing the type, order, default values, and specifiers for a method's parameters will allow you to explore and invoke methods dynamically.

Reflecting Any Member

You can use the Type.GetMember and Type.GetMembers methods to request a MemberInfo object or an array of MemberInfo objects, respectively. A MemberInfo object is a general form of a specific type record. GetMembers , for example, will return all the members for a type, including methods, fields, properties, and events.

Reflecting Properties

When you reflect properties, you will need to keep in mind that there is a property setter and a property getter. The setter behaves like a subroutine and takes an argument to set the underlying field of the property, and the getter behaves like a function, returning a value. Listing 4.10 contains code that demonstrates how to set and get the value of a property using Reflection.

Listing 4.10 Setting and Getting Property Members by Using Reflection
 Dim Instance As ReflectMembers = New ReflectMembers() Dim T As Type = Instance.GetType() T.InvokeMember("PublicProperty", _   BindingFlags.SetProperty Or BindingFlags.Public _   Or BindingFlags.Instance, _   Nothing, Instance, New Object() {"Naughties"}) Dim Value As Object Value = T.InvokeMember("PublicProperty", _   BindingFlags.GetProperty Or BindingFlags.Public Or _   BindingFlags.Instance, _   Nothing, Instance, New Object() {}) Console.WriteLine("PublicProperty=" + Value.ToString()) 

In summary, Listing 4.10 requests a Type object. The Type object is used to call InvokeMember twice, setting and getting a property value. The first InvokeMember call sets the value of the property, working approximately like a method invocation. We use the enumerated value BindingFlags.SetProperty to indicate that we want to invoke a property setter, and we pass the parameter based on the type of the property. ( PublicProperty is defined as a string property.)

The second call to InvokeMember uses BindingFlags.GetProperty to request the getter. The second invocation behaves like a function, as illustrated by the assignment to the return Value . If we have specified Option Strict On , then Value must be an Object type because InvokeMember returns an object. If Option Strict is Off , then Value can be the type we know InvokeMember is returning; in Listing 4.10 it returns a string.

Reflecting Fields

As a general guideline fields are always private. In conjunction with that guideline, fields are usually exposed via public properties. This section contains some examples that demonstrate various kinds of field values. Like properties, field values can be used as right-hand-side and left-hand-side values. When you reflect a field, you need to indicate whether you want to get or set the field value.

Reflecting Public Fields

The code in Listing 4.11 demonstrates how to use BindingFlags.GetField to use the value of a Reflected field as a right-hand-side value and BindingFlags.SetField to use a field as a left-hand-side value. The code in Listing 4.11 assigns a public field the string value of the name of an intense action thriller, Training Day .

Listing 4.11 Reflecting a Public Field by Using GetField and SetField
 Dim Instance As ReflectMembers = New ReflectMembers() Dim T As Type = Instance.GetType() T.InvokeMember("PublicField", _   BindingFlags.SetField Or BindingFlags.Public _   Or BindingFlags.Instance, _   Nothing, Instance, New Object() {"Training Day"}) Dim Value As Object Value = T.InvokeMember("PublicField", _   BindingFlags.GetField Or BindingFlags.Public Or _   BindingFlags.Instance, _   Nothing, Instance, New Object() {}) Console.WriteLine("PublicField=" + Value.ToString()) 

If you compare Listings 4.10 and 4.11 you will quickly see that they are very similar. The biggest difference is that we are referring to a field in Listing 4.11; hence we use BindingFlags.GetField when we want to read the value of a field and BindingFlags.SetField when we want to write the value of a field.

Reflecting Nonpublic Fields

A more likely scenario is that you will want to obtain the value of fields that are not public. We'll add a twist. The Reflection code in Listing 4.12 incorporates a custom binder. The custom binder (defined in Listing 4.13 in the next subsection) supports assigning a string of digits to an integer field. That is, the binder performs a type conversion when it makes sense to do so, as it does when the value we are assigning to the field is the string representation of an integer.

There is one other issue suggested by this code: security. If any code could load an assembly and use Reflection to poke around in nonpublic areas of your types, the potential for abuse is serious. Fortunately Microsoft has an aggressive security model, and you can prevent code from reflecting nonpublic members and types (see the Understanding Reflection and Security section later in this chapter).

Here is Listing 4.12.

Listing 4.12 Reflecting a Private Field Value and Using a Custom Binder
 1:  Private Sub ReflectPrivateField() 2:    Dim Instance As ReflectMembers = New ReflectMembers() 3: 4:    Dim T As Type = Instance.GetType() 5:    T.InvokeMember("PrivateField", _ 6:      BindingFlags.SetField Or BindingFlags.NonPublic _ 7:      Or BindingFlags.Instance, _ 8:      New SimpleBinder(), Instance, New Object() {"13"}) 9: 10:   Dim Value As Object 11: 12:   Value = T.InvokeMember("PrivateField", _ 13:     BindingFlags.GetField Or BindingFlags.NonPublic Or _ 14:     BindingFlags.Instance, _ 15:     Nothing, Instance, New Object() {}) 16: 17:   Console.WriteLine("PrivateField=" + Value.ToString()) 18: 19: End Sub 

Lines 5 through 8 represent using a private field as a left-hand-side value. BindingFlags.SetField means we will be providing a value for the field, and BindingFlags.NonPublic indicates that we are looking for a nonpublic field. Lines 12 through 15 represent using the field as a right-hand-side value.

You know that the field PrivateField is an integer because I am telling you that it is. However, notice that the array of arguments in line 8 is passing a string value "13" to the field. This works because I implemented a custom binder, which I am also passing to InvokeMember . SimpleBinder is defined in the next subsection in Listing 4.13.

Implementing a Simple Custom Binder

Binders are used with Reflection (as demonstrated in Listing 4.12) to convert values, and binders are also used to do things like bind fields to controls in ASP.NET Web pages.

The System.Reflection.Binder class is a MustInherit class. In the object-oriented vernacular, MustInherit classes are called virtual abstract classes . Technically speaking, a MustInherit class will have several members that must be overridden; that is, several members are adorned with the MustOverride modifier.

SimpleBinder in Listing 4.13 provides only the service of converting a string to an integer. As a result you will see that I provided an implementation for all the MustOverride methods for SimpleBinder , but all the methods except ChangeType are stubs.

Listing 4.13 Implementing a Custom Binder
 1:  Public Class SimpleBinder 2:    Inherits System.Reflection.Binder 3: 4:    Public Overrides Function ChangeType( _ 5:       ByVal value As Object, _ 6:       ByVal type As Type, _ 7:       ByVal culture As CultureInfo _ 8:    ) As Object 9: 10:     Try 11:       If (value.GetType() Is GetType(String)) Then 12:         Return CInt(value) 13:       Else 14:         Return value 15:       End If 16:     Catch 17:       Return value 18:     End Try 19: 20:   End Function 21: 22:   Public Overrides Function SelectProperty( _ 23:      ByVal bindingAttr As BindingFlags, _ 24:      ByVal match() As PropertyInfo, _ 25:      ByVal returnType As Type, _ 26:      ByVal indexes() As Type, _ 27:      ByVal modifiers() As ParameterModifier _ 28:   ) As PropertyInfo 29: 30:   End Function 31: 32:   Public Overrides Function BindToMethod( _ 33:      ByVal bindingAttr As BindingFlags, _ 34:      ByVal match() As MethodBase, _ 35:      ByRef args() As Object, _ 36:      ByVal modifiers() As ParameterModifier, _ 37:      ByVal culture As CultureInfo, _ 38:      ByVal names() As String, _ 39:      ByRef state As Object _ 40:   ) As MethodBase 41: 42:   End Function 43: 44:   Public Overrides Function BindToField( _ 45:      ByVal bindingAttr As BindingFlags, _ 46:      ByVal match() As FieldInfo, _ 47:      ByVal value As Object, _ 48:      ByVal culture As CultureInfo _ 49:   ) As FieldInfo 50: 51:   End Function 52: 53: 54:   Public Overrides Sub ReorderArgumentArray( _ 55:      ByRef args() As Object, _ 56:      ByVal state As Object _ 57:   ) 58: 59:   End Sub 60: 61:   Public Overrides Function SelectMethod( _ 62:      ByVal bindingAttr As BindingFlags, _ 63:      ByVal match() As MethodBase, _ 64:      ByVal types() As Type, _ 65:      ByVal modifiers() As ParameterModifier _ 66:   ) As MethodBase 67: 68:   End Function 69: 70: End Class 

To create a binder you must inherit from System.Reflection.Binder and provide an implementation for ChangeType , SelectProperty , BindToMethod , BindToField , ReorderArgumentArray , and SelectMethod . Listing 4.13 provides stubs that satisfy this requirement. The only method that does anything in the listing is ChangeType .

ChangeType is implemented in lines 4 through 20. The biggest part of the function is the long parameter list. For our purposes all we want to do is determine whether the value parameter can be converted to an integer and return the converted integer. In all other cases we return the value parameter; the conversion is 7 lines long, spanning lines 10 through 16.

Reflecting Events

Rounding out the implementation of members we can reflect are events, which are critically important to Windows-based programming (and all event-driven operating systems). Reflection supports discovering events and assigning event handlers to Reflected events, as demonstrated in Listing 4.14.

Listing 4.14 Assigning an Event Handler to a Reflected Event
 1:  Private Sub ReflectEvent() 2:    Dim Instance As ReflectMembers = New ReflectMembers() 3: 4:    Dim T As Type = Instance.GetType() 5:    Dim [Event] As EventInfo 6: 7:    [Event] = T.GetEvent("PublicEvent", _ 8:      BindingFlags.Public Or BindingFlags.Instance) 9: 10:   [Event].AddEventHandler(Instance, New _ 11:     EventHandler(AddressOf Handler)) 12:   Instance.DoEvent() 13: 14:   End Sub 

Line 4 gets the Type object for ReflectMembers created on line 2. An EventInfo variable is declared on line 5. (Remember you can use keywords by putting them in brackets, but you may want to avoid doing so.) Line 7 gets the EventInfo object by calling GetEvent and naming the event. (I used instructional names for the members of ReflectMembers ; that's why there is an event named PublicEvent .)

Line 10 uses the EventInfo object to call AddEventHandler . AddEventHandler needs an object to associate the handler with ( Instance ), and System.Delegate is constructed and initialized with the address of a method that matches the EventHandler signature. (Refer to Chapter 3 on delegates.) Finally, to test that everything is wired correctly, line 12 calls a method that raises the event.



Visual Basic. NET Power Coding
Visual Basic(R) .NET Power Coding
ISBN: 0672324075
EAN: 2147483647
Year: 2005
Pages: 215
Authors: Paul Kimmel

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