Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 26 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
26
Dung lượng
823,25 KB
Nội dung
Reflection One of the most remarkable new features introduced by .NET was reflection. At its simplest, reflec- tion is the process of allowing a program to discover information about code, possibly even about its own code. Reflection allows a program to learn about an assembly, either when it is running, or from its compiled DLL or executable code. It lets a program discover the types, properties, meth- ods, and events defined by an assembly. It lets a program dynamically load the assembly, create instances of its types, and invoke its methods all at run-time. It even lets a program generate, com- pile, and execute new code completely from scratch. Even if you don’t expect your program to interact with other assemblies, you may find some reflection techniques useful. For example, if your application uses only its own modules, you may not think you need to use reflection to learn about what is contained in those modules. After all, you are writing the code, so you should know what it contains. However, it may still be useful for the program to load assemblies at run-time. For example, the program can look in a tools directory to see what assemblies are available and load any tools that they define. Then, if you later decide to add, modify, or remove a tool, you only need to add, replace, or remove an assembly from this directory, and the program will automatically update itself the next time it runs. That makes distributing new tools to users quick and easy. There isn’t enough room in this chapter to cover all of the reflection tools provided by the .NET Framework, but there is enough room to cover some of the most useful of those tools. That should give you enough of a start that you can figure out how to use other tools as needed. Exploring Assemblies An obvious first task when using reflection is learning information about an assembly. An assembly is the smallest unit of deployment in a .NET application. It is basically a piece of compiled code. Generally, that means a compiled .exe or .dll file. 29_053416 ch22.qxd 1/2/07 6:37 PM Page 559 Example program GetAssemblyInformation (available for download at www.vb-helper.com/one_ on_one.htm ) displays information about an assembly stored in a file. It’s a long and involved program, but its basic operations are relatively simple when taken individually. The program doesn’t even try to display all of the possible information about the assembly. Instead, it sticks to the most common and useful bits of information. When you enter the path to an assembly and click the Go button, the program executes the following code: ‘ Display information about the assembly. Private Sub btnGo_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnGo.Click Dim assem As [Assembly] = [Assembly].LoadFrom(txtPath.Text) ‘ Display the assembly’s full name. trvDetails.Nodes.Clear() trvDetails.Nodes.Add(“Full Name: “ & assem.FullName) trvDetails.Nodes.Add(“Location: “ & assem.Location) ‘ Display referenced assemblies. Dim ref_assem_node As TreeNode = trvDetails.Nodes.Add(“Referenced Assemblies”) For Each referenced_assembly As AssemblyName In assem.GetReferencedAssemblies() ref_assem_node.Nodes.Add(referenced_assembly.FullName) Next referenced_assembly ‘ Display the assembly’s entry point. Dim entry_point As MethodInfo = assem.EntryPoint() trvDetails.Nodes.Add(“Entry Point: “ & entry_point.Name) ‘ Types. Dim types_node As TreeNode = trvDetails.Nodes.Add(“Types”) For Each exported_type As Type In assem.GetTypes() AddTypeData(types_node, exported_type) Next exported_type End Sub First, the code creates an Assembly object based on the assembly file you entered in the txtPath text box. It then clears the trvDetails TreeView control, where it will store its results, and adds the assem- bly’s full name (name, version, culture, and token) and its location to the tree view. The code then adds a node labeled Referenced Assemblies to the tree view. It loops through the val- ues in the assemblies returned by the Assembly object’s GetReferencedAssemblies function, adding each assembly’s name to the tree view. This code shows a common operation when exploring an assembly: the code finds an object and loops through an array representing items that are logically contained in the original object. In this case, the Assembly object provides a list of referenced assemblies. Similarly, a Type object representing a class can have properties, fields, methods, events, and nested types. The code adds the assembly’s entry point to the tree view, and then loops through the exported (public) types defined by the assembly. For each of these types, the code calls subroutine AddTypeData to add information about the type to the tree view. The following code shows subroutine AddTypeData: 560 Part IV: Specific Techniques 29_053416 ch22.qxd 1/2/07 6:37 PM Page 560 ‘ Return information about a type. Private Sub AddTypeData(ByVal parent_node As TreeNode, ByVal the_type As Type) Dim txt As String = “” If the_type.IsPublic Then txt &= “Public “ If the_type.IsInterface Then ‘ Skip MustInherit for Interfaces. txt &= “Interface “ Else If the_type.IsAbstract Then txt &= “MustInherit “ End If If the_type.IsClass Then txt &= “Class “ If the_type.IsEnum Then txt &= “Enum “ If the_type.IsValueType Then txt &= “Structure “ Dim type_name As String = the_type.Name If the_type.ContainsGenericParameters Then ‘ Remove whatever comes after `. type_name = type_name.Substring(0, type_name.IndexOf(“`”)) ‘ Compose the generic parameters. Dim generic_params As String = “” Dim generic_type As Type = the_type.GetGenericTypeDefinition For Each arg_type As Type In generic_type.GetGenericArguments() generic_params &= “, “ & arg_type.Name Next arg_type If generic_params.Length > 0 Then _ generic_params = generic_params.Substring(2) type_name &= “(Of “ & generic_params & “)” End If Dim type_node As TreeNode = parent_node.Nodes.Add(txt & type_name) If the_type.BaseType Is Nothing Then type_node.Nodes.Add(“Inherits From: Nothing”) Else type_node.Nodes.Add(“Inherits From: “ & the_type.BaseType.FullName) End If type_node.Nodes.Add(“Attributes: “ & the_type.Attributes.ToString) type_node.Nodes.Add(“Qualified Name: “ & the_type.AssemblyQualifiedName) ‘ See if it’s an enum. If the_type.IsEnum Then ‘ It’s an enum. List its values. Dim values_node As TreeNode = type_node.Nodes.Add(“Values”) For Each field_info As FieldInfo In the_type.GetFields() If field_info.IsStatic Then values_node.Nodes.Add(field_info.Name & “ = “ & _ CLng(field_info.GetValue(Nothing))) Else values_node.Nodes.Add(field_info.Name & “ (non static)”) End If Next field_info Else ‘ It’s not an enum. ‘ Interfaces. Dim ifaces() As Type = the_type.GetInterfaces() 561 Chapter 22: Reflection 29_053416 ch22.qxd 1/2/07 6:37 PM Page 561 If ifaces.Length > 0 Then Dim ifaces_node As TreeNode = type_node.Nodes.Add(“Interfaces”) For Each iface_type As Type In ifaces AddInterfaceData(ifaces_node, iface_type) Next iface_type End If ‘ Constructors. Dim constructor_infos() As ConstructorInfo = the_type.GetConstructors() ‘ If constructor_infos = Nothing, ‘ the type has only a default constructor. ‘ If constructor_infos is empty, it has no constructors. If constructor_infos Is Nothing Then ‘ It has no explicit constructors, ‘ only the default empty constructor. Dim constr_node As TreeNode = type_node.Nodes.Add(“Constructors”) constr_node.Nodes.Add(“New “ & type_name & “()”) ElseIf constructor_infos.Length > 0 Then Dim constr_node As TreeNode = type_node.Nodes.Add(“Constructors”) For Each constructor_info As ConstructorInfo In constructor_infos AddConstructorData(constr_node, type_name, constructor_info) Next constructor_info End If ‘ Fields. Dim field_infos() As FieldInfo = the_type.GetFields() If field_infos.Length > 0 Then Dim fields_node As TreeNode = type_node.Nodes.Add(“Fields”) For Each field_info As FieldInfo In field_infos AddFieldData(fields_node, field_info) Next field_info End If ‘ Properties. Dim property_infos() As PropertyInfo = the_type.GetProperties() If property_infos.Length > 0 Then Dim properties_node As TreeNode = type_node.Nodes.Add(“Properties”) For Each property_info As PropertyInfo In property_infos AddPropertyData(properties_node, property_info) Next property_info End If ‘ Methods. Dim method_infos() As MethodInfo = the_type.GetMethods() If method_infos.Length > 0 Then Dim methods_node As TreeNode = type_node.Nodes.Add(“Methods”) For Each method_info As MethodInfo In method_infos AddMethodData(methods_node, method_info) Next method_info End If ‘ Events. Dim event_infos() As EventInfo = the_type.GetEvents() If event_infos.Length > 0 Then 562 Part IV: Specific Techniques 29_053416 ch22.qxd 1/2/07 6:37 PM Page 562 Dim events_node As TreeNode = type_node.Nodes.Add(“Events”) For Each event_info As EventInfo In event_infos AddEventData(events_node, event_info) Next event_info End If ‘ Nested types. Dim nested_types() As Type = the_type.GetNestedTypes() If nested_types.Length > 0 Then Dim nested_types_node As TreeNode = type_node.Nodes.Add(“Nested Types”) For Each nested_type As Type In nested_types AddTypeData(nested_types_node, nested_type) Next nested_type End If End If End Sub Subroutine AddTypeData takes a Type object as a parameter. It checks the Type’s properties to deter- mine whether the type is public, an interface, MustInherit, a class, an enumerated type, or a structure. It uses this information to build an appropriate declaration string for the type. Next, the routine checks whether the type contains generic parameters, as in Public Class TestClass(Of T1, T2) . If it does, the code loops through the generic arguments to build an appropri- ate parameter list. The code then adds a node to the tree view containing the type’s basic declaration. The code adds any other tree view nodes for this type as descendants of this node. The subroutine adds entries, giving the type from which this one inherits (note that interfaces don’t inherit from anything), its attributes, and its fully qualified name. It then considers other items that are logically contained within the type. If the type is an Enum, the code loops through its fields. If a field is static (has a constant value), the code displays its name and its value. If a field is not static, the code displays the field’s name. If the type is not an Enum, the subroutine loops through and displays information about the interfaces it implements. It then describes the type’s constructors, fields, properties, methods, events, and nested types (for example, classes defined within this class). For each of these more-complicated items, the AddTypeData subroutine calls other routines to provide additional information. For example, for a constructor, it calls subroutine AddConstructorData. For a nested type, subroutine AddTypeData calls itself recursively to describe the nested type. The other routines used by the program are much simpler. Subroutine AddConstructorData shown in the following code adds a node to the tree view describing a constructor. It starts with the keyword New and uses function ParameterList to list its parameters. ‘ Return information about a type. Private Sub AddConstructorData(ByVal parent_node As TreeNode, _ ByVal type_name As String, ByVal constructor_info As ConstructorInfo) ‘ Get the constructor’s parameters. Dim txt As String = “New “ & type_name & _ 563 Chapter 22: Reflection 29_053416 ch22.qxd 1/2/07 6:37 PM Page 563 ParameterList(constructor_info.GetParameters()) parent_node.Nodes.Add(txt) End Sub Function ParameterList loops through an array of ParameterInfo objects and calls GetParameter Data to get a declaration for each. ‘ Return a string listing parameters from a collection. Private Function ParameterList(ByVal param_infos() As ParameterInfo) As String Dim params As String = “” For Each parameter_info As ParameterInfo In param_infos params &= “, “ & GetParameterData(parameter_info) Next parameter_info If params.Length > 0 Then params = params.Substring(2) Return “(“ & params & “)” End Function Function GetParameterData examines a ParameterInfo object and builds its declaration as it would appear within a parameter list. This function is somewhat limited. For example, it doesn’t try to com- pletely interpret parameters that are of generic types as in Sub Test(ByVal values As List(Of Boolean)) . ‘ Return information about a parameter. Private Function GetParameterData(ByVal parameter_info As ParameterInfo) As String Dim txt As String = “” If parameter_info.IsOptional Then txt &= “Optional “ End If If parameter_info.IsOut Then txt &= “ByRef “ Else txt &= “ByVal “ End If txt &= parameter_info.Name If parameter_info.ParameterType.IsArray Then txt &= “()” txt &= “ As “ & parameter_info.ParameterType.GetElementType.Name Else txt &= “ As “ & parameter_info.ParameterType.Name End If If parameter_info.IsOptional Then If parameter_info.ParameterType.Name = “String” Then txt &= “ = “”” & parameter_info.DefaultValue.ToString() & “””” ElseIf parameter_info.DefaultValue Is Nothing Then txt &= “ = Nothing” Else txt &= “ = “ & parameter_info.DefaultValue.ToString() End If End If Return txt End Function 564 Part IV: Specific Techniques 29_053416 ch22.qxd 1/2/07 6:37 PM Page 564 Subroutines AddFieldData and AddPropertyData, shown in the following code, examine FieldInfo and PropertyInfo objects to provide reasonable declarations for fields (public variables) and properties. ‘ Add information about a field. Private Sub AddFieldData(ByVal fields_node As TreeNode, _ ByVal field_info As FieldInfo) Dim txt As String = “” If field_info.IsPublic Then txt &= “Public “ If field_info.IsPrivate Then txt &= “Private “ If field_info.IsFamily Then txt &= “Protected “ If field_info.IsStatic Then txt &= “Shared “ txt &= field_info.Name txt &= “ As “ & field_info.FieldType.Name fields_node.Nodes.Add(txt) End Sub ‘ Add information about a property. Private Sub AddPropertyData(ByVal properties_node As TreeNode, _ ByVal property_info As PropertyInfo) Dim txt As String = “Public “ If property_info.CanRead AndAlso Not property_info.CanWrite Then txt &= “ReadOnly “ ElseIf Not property_info.CanRead AndAlso property_info.CanWrite Then txt &= “WriteOnly “ End If txt &= property_info.Name txt &= “ As “ & property_info.PropertyType.Name properties_node.Nodes.Add(txt) End Sub The following code shows how subroutine AddMethodData displays information about a method. It simply calls function MethodSignature to compose a description for a method. Function MethodSignature determines the method’s visibility, and whether it is a subroutine or function. It adds the method’s name and uses function ParameterList to add its parameters. ‘ Add information about a method. Private Sub AddMethodData(ByVal methods_node As TreeNode, _ ByVal method_info As MethodInfo) methods_node.Nodes.Add(MethodSignature(method_info)) End Sub ‘ Return a method’s signature in a VB format. Private Function MethodSignature(ByVal method_info As MethodInfo) As String Dim txt As String = “” If method_info.IsFamilyAndAssembly Then txt &= “Protected Friend “ ElseIf method_info.IsFamily Then txt &= “Protected “ ElseIf method_info.IsFamilyOrAssembly Then txt &= “Friend “ ElseIf method_info.IsPrivate Then txt &= “Private “ ElseIf method_info.IsPublic Then 565 Chapter 22: Reflection 29_053416 ch22.qxd 1/2/07 6:37 PM Page 565 txt &= “Public “ End If If method_info.IsFinal Then txt &= “NotOverridable “ If method_info.IsHideBySig Then txt &= “Overrides “ If method_info.IsAbstract Then txt &= “MustOverride “ If method_info.IsStatic Then txt &= “Shared “ If method_info.ReturnType.Name = “Void” Then txt &= “Sub “ Else txt &= “Function “ End If txt &= method_info.Name txt &= ParameterList(method_info.GetParameters()) If method_info.ReturnType.Name <> “Void” Then txt &= “ As “ & method_info.ReturnType.Name End If Return txt End Function Subroutine AddEventData simply adds an event’s name to the tree view, and subroutine AddInterfaceData just adds the keyword Implements and the interface’s name to the output. ‘ Add information about an event. Private Sub AddEventData(ByVal events_node As TreeNode, _ ByVal event_info As EventInfo) events_node.Nodes.Add(event_info.Name) End Sub ‘ Add information about an implemented interface. Private Sub AddInterfaceData(ByVal ifaces_node As TreeNode, _ ByVal iface_type As Type) ifaces_node.Nodes.Add(“Implements “ & iface_type.Name) End Sub Figure 22-1 shows program GetAssemblyInformation in action. In this figure, the Employee class is open and displaying its interfaces, constructors, fields, and properties. You can download the example program at www.vb-helper.com/one_on_one.htm and look at the Employee class it defines to see how it leads to the results shown in Figure 22-1. Example program GetMyAssemblyInformation is almost the same as program GetAssemblyInformation, except it doesn’t load an Assembly object from a file. Instead this program uses the following statement to get an Assembly representing itself as it is running: Dim assem As [Assembly] = [Assembly].GetExecutingAssembly() This is reflection in a fairly literal sense. The code is actually examining at itself as if it were looking in a mirror. 566 Part IV: Specific Techniques 29_053416 ch22.qxd 1/2/07 6:37 PM Page 566 Figure 22-1: Program GetAssemblyInformation displays information about an assembly. You may find program GetAssemblyInformation useful for exploring assemblies, but it’s more important as an example showing how you can search through an assembly for particular information. For example, it shows how you can search an assembly to find the classes it defines. It also shows how to examine those classes to see which ones provide default constructors that take no parameters. Your program can easily make instances of those classes and possibly use their methods. Example programs UseReflectionTools and UseDiscoveryLib (described later in this chapter) demonstrate this technique. Exploring Enumerations The .NET Framework includes many enumerations, including some that are very large such as HatchStyle with 56 entries and Color with 141 entries. Suppose you are building a drawing program and you want to let the user pick from among these values. Building the lists of HatchStyles and Colors would be a lot of work. It would also make maintenance more difficult. If a later release of the .NET Framework defined new colors, for example, you would have to figure out which ones were new and add them to your list. An alternative solution is to use reflection to make the HatchStyle and Color types list their own values. Example program ShowHatchBrushes (available for download at www.vb-helper.com/one_on_ one.htm ) uses the following code to show samples of the defined HatchStyle values: ‘ Display brush samples. Private Sub Form1_Paint(ByVal sender As Object, _ 567 Chapter 22: Reflection 29_053416 ch22.qxd 1/2/07 6:37 PM Page 567 ByVal e As System.Windows.Forms.PaintEventArgs) Handles Me.Paint e.Graphics.Clear(Me.BackColor) Dim x As Integer = XMIN Dim y As Integer = XMIN ‘ Enumerate the HatchBrush styles. Dim hatch_style_type As Type = GetType(HatchStyle) Dim field_infos() As FieldInfo = hatch_style_type.GetFields() For Each field_info As FieldInfo In field_infos ‘ See if this is a static (Shared) property. ‘ We only want the Shared properties such as Horizontal ‘ and not the instance properties such as value__. If field_info.IsStatic Then ‘ Get this brush. Dim style_obj As Object = field_info.GetValue(Nothing) Dim hatch_style As HatchStyle = DirectCast(style_obj, HatchStyle) ‘ Make the brush and draw the sample. Using br As New HatchBrush(hatch_style, Color.Black, Color.White) DrawSample(e.Graphics, x, y, br, field_info.Name) End Using End If Next field_info End Sub ‘ Draw the sample and move (x, y) to a new position. Private Sub DrawSample(ByVal gr As Graphics, ByRef x As Integer, _ ByRef y As Integer, ByVal br As Brush, ByVal brush_name As String) ‘ Draw the sample. gr.FillRectangle(br, x, y, SAMPLE_WID, SAMPLE_HGT) gr.DrawRectangle(Pens.Red, x, y, SAMPLE_WID, SAMPLE_HGT) ‘ Draw the brush’s name. Dim string_format As New StringFormat() string_format.Alignment = StringAlignment.Center string_format.LineAlignment = StringAlignment.Near gr.DrawString(brush_name, Me.Font, Brushes.Blue, _ x + SAMPLE_WID \ 2, y + SAMPLE_HGT, string_format) ‘ Update x and y. x += SAMPLE_WID + X_MARGIN If x + SAMPLE_WID > Me.ClientSize.Width Then x = XMIN y += SAMPLE_HGT + Y_MARGIN End If End Sub The form’s Paint event handler gets a Type object representing the HatchStyle type. It then loops through the FieldInfo objects representing the type’s fields. It uses each FieldInfo’s IsStatic method to determine whether a field is a constant, and calls subroutine DrawSample to draw a sample of the static fields. 568 Part IV: Specific Techniques 29_053416 ch22.qxd 1/2/07 6:37 PM Page 568 [...]...29_053416 ch22.qxd 1/2/07 6:37 PM Page 569 Chapter 22: Reflection Subroutine DrawSample fills a rectangle with the sample brush, draws a rectangle around it, and displays the style’s name underneath It then updates the X and Y coordinates where it will draw the next sample Figure 2 2-2 shows program ShowHatchBrushes displaying the hatch styles defined by the HatchStyle type Figure 2 2-2 : Program ShowHatchBrushes... management as much as possible, and rely on the NET Framework and run-time libraries to handle memory issues While that usually works, there are a few things you can do to make your applications control their memory use more intelligently Chapter 23, “Memory Management,” describes Visual Basic s memory model and some of the ways you can handle memory efficiently 584 ... To add file resources such as image files and text files to the project, open Solution Explorer and right-click the Resources folder In the context menu, open the Add submenu and select the Existing Item command Select the file you want to add to the project and click Add To embed the file in the project, go to Solution Explorer, open the Resources folder, and select the file Then in the Properties... LcmCalculator class, picked the GCD function, and clicked Run The program displayed the InputBox and the user entered the string “24,92.” When the user clicks OK, the program will call the GCD function, passing it the values 24 and 92 In this example, GCD returns the greatest common divisor of 24 and 92, which is 4, so the program displays the result 4 Figure 2 2-4 : Program UseDiscoveryLib executes methods... available to run-of-the-mill end users, you would probably want to restrict the code somewhat For example, if an application often needs to use numeric calculations, you might want to search the assemblies in a specific directory for functions that take and return numeric parameters You probably wouldn’t want to let users run the GetHashCode command (which won’t be meaningful) or the GetType command (which... class’s name and isn’t very useful) Discovering Resources The examples described earlier in this chapter manipulate code, open an assembly, and use its code in different ways Some list values in enumerations Others create objects and execute code Another useful way to manipulate an assembly is to examine and use its resources Example project ResourcesLib (available for download at www.vb-helper.com/one_on_one.htm)... XMIN + 6 * SAMPLE_HGT + 5 * Y_MARGIN) End Sub The form’s Paint event handler draws samples of the colors in the m_Colors array Download the code to see the details Figure 2 2-3 shows program ShowSystemColors in action Figure 2 2-3 : Program ShowSystemColors draws samples of the values defined by SystemColors The following table lists this chapter s example programs that display samples of enumerations All... resource Figure 2 2-6 : Program GetResource searches an assembly for embedded resources Retrieving Known Resources In addition to containing embedded resources, an assembly can hold resources that you build in Visual Studio’s embedded resource editor For example, to add a string resource, open Solution Explorer, double-click My Project, and select the Resources tab In the leftmost drop-down, select Strings... RodStephens@vb-helper.com and I’ll add the information to the book’s Web site Example program GetKnownResources (available for download at www.vb-helper.com/one_on_ one.htm) uses a ResourceManager to fetch this kind of resource Its list box lists the resources contained in the ResourceLib program, including the Greeting string resource 580 29_053416 ch22.qxd 1/2/07 6:37 PM Page 581 Chapter 22: Reflection... returns the greatest common divisor of two numbers) and LCM (which returns the least common multiple of two numbers) The GCD of two numbers is the largest integer that evenly divides them both The LCM of two numbers is the smallest integer that is a multiple of them both 576 29_053416 ch22.qxd 1/2/07 6:37 PM Page 577 Chapter 22: Reflection Figure 2 2-4 shows program UseDiscoveryLib in action Here, the . to Embedded Resource. Figure 2 2-5 shows the properties for the file BigJack.jpg. 577 Chapter 22: Reflection 29_053416 ch22.qxd 1/2/07 6:37 PM Page 577 Figure 2 2-5 : Set a resource file’s Build. take and return numeric parame- ters. You probably wouldn’t want to let users run the GetHashCode command (which won’t be mean- ingful) or the GetType command (which returns the class’s name and. particu- lar it explains how you can make an application compile and execute new Visual Basic code at run-time. Using this technique, you can add new features to a previously compiled application. Chapter