Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

CSharp Bible (2002) [eng]

.pdf
Скачиваний:
55
Добавлен:
16.08.2013
Размер:
4.29 Mб
Скачать

In this chapter, you learn to use the Reflection namespace for examining objects at runtime. You also learn to late bind to objects and use methods and properties within these late bound objects.

Understanding the Type Class

The Type class acts as a window into the Reflection API, enabling access to metadata. The abstract class, System.Type, represents a type in the Common Type System (CTS). This Common Type System is what enables you to examine objects across all languages in the

.NET family. Because each object uses the same framework, runtime, and type system, object and type information is easily obtainable.

One of the Type classes greatest assets is the capability to create objects dynamically and use them at runtime.

Retrieving type Information

Type information can be retrieved from objects using several methods. The following sections describe how to do this in three different ways: by using a type name, by using a process name, or by specifying an assembly name to retrieve the information. Although all of these implementations perform essentially the same task, each is useful in its own right. Depending on the requirements of your application, you might only need to use one form of the typegathering abilities.

Retrieving types by name

By simply specifying the name of a type, you can query almost all aspects of the object. You can determine whether the object is a class, what its base system type is, and many other properties.

To test this, you can build a simple application to view some properties of the System.String class, as shown in Listing 32-1.

Listing 32-1: Querying Type Information by Name

using System;

using System.Reflection;

class NameType

{

public static void Main()

{

Type t = Type.GetType("System.String"); Console.WriteLine("Name : {0}",t.Name); Console.WriteLine("Underlying System Type :

{0}",t.UnderlyingSystemType); Console.WriteLine("Is Class : {0}",t.IsClass);

}

}

Figure 32-1 shows that System.String is the base system type, and that the object is indeed a class.

Figure 32-1: Query type information by object name.

You may be wondering why this information is useful. Suppose you are building an application that needs to generate insert statements to put information into SQL Server. Typing a large amount of information takes an extremely long time. Using Reflection and the Type class, you can examine the underlying type of each piece of information you want to insert into SQL Server and map those types to a valid SQL Server data type. This greatly eases the process of programmatically generating the insert statements that you require.

Retrieving types by instance

Rather than use the name of a type, you may simply use an instance of an object that you want to examine. Listing 32-2 represents an example similar to the preceding one.

Listing 32-2: Querying Type Information Using the Instance of an Object

using System;

using System.Reflection;

class InstanceType

{

public static void Main()

{

String myVar = "Brian Patterson"; Type t = myVar.GetType();

Console.WriteLine("Name : {0}",t.Name); Console.WriteLine("Underlying System Type :

{0}",t.UnderlyingSystemType);

Console.WriteLine("Is Class : {0}",t.IsClass);

}

}

In this example, rather than specify that you want to view type information of System.String, you create an instance of a string variable on which you then call the GetType() method. The information obtained here is the same as that obtained in the preceding example, the difference being that you don't have to know the type before the fact. You simply call the GetType() method and assign it to a Type object, which you can the query for the name, underlying system type, and so on.

Retrieving types in an assembly

Many times, you will want to examine types contained within an assembly. This assembly could be an executable or even a dynamic link library contained on the system.

Listing 32-3 contains the code necessary to examine the type information of the executable itself.

Listing 32-3: Examining a Currently Running Process for Type Information

using System;

using System.Reflection; using System.Diagnostics;

class AssemType

{

public static void Main(string[] args)

{

Process p = Process.GetCurrentProcess(); string assemblyName = p.ProcessName + ".exe";

Console.WriteLine("Examining : {0}", assemblyName); Assembly a = Assembly.LoadFrom(assemblyName);

Type[] types = a.GetTypes(); foreach(Type t in types)

{

Console.WriteLine("\nType : {0}",t.FullName); Console.WriteLine("\tBase class :

{0}",t.BaseType.FullName);

}

}

}

The preceding code introduces a few new items. The process type is used to examine running processes. In this context, you use it to get the name of your program, and then append .exe to the end of the name so you can examine the assembly. You can just as easily hard code in the name of your application, but this method ensures that it will work no matter what you name the application.

Figure 32-2 shows the output for this application. Not a very dramatic result, as your program contains only one class.

Figure 32-2: Process information obtained with the Reflection API

As you can see in Figure 32-2, you have examined the current running process and the actual application itself and displayed all internal classes and their types. For the sake of experimentation, trying adding a few more dummy classes to this project and then rerun the application. You should see a list of all the classes contained therein, as well as their types.

Interrogating objects

Listing 32-4 contains the source listing for the ReflectionTest application, which examines a class and provides the details about that class. This application is a good conglomerate of everything you learned about Reflection up to this point.

Listing 32-4: Class Objects Are Easily Transversed for Member Information

namespace ReflectionTest

{

using System ;

using System.Reflection ;

public class Class1

{

public static int Main ( )

{

Type t = typeof( aUsefulClass ) ; Console.WriteLine ( "Type of class: " + t ) ; Console.WriteLine ( "Namespace: " + t.Namespace ) ; ConstructorInfo[] ci = t.GetConstructors( );

Console.WriteLine("-----------------------------------------------

");

Console.WriteLine( "Constructors are:" ) ; foreach( ConstructorInfo i in ci )

{

Console.WriteLine( i ) ;

}

 

PropertyInfo[] pi = t.GetProperties( );

");

Console.WriteLine("-----------------------------------------------

Console.WriteLine( "Properties are:" ) ;

 

foreach( PropertyInfo i in pi )

 

{

 

Console.WriteLine( i ) ;

 

}

 

MethodInfo[] mi = t.GetMethods( ) ;

 

Console.WriteLine("-----------------------------------------------

");

Console.WriteLine( "Methods are:" ) ;

 

foreach( MethodInfo i in mi )

{

Console.WriteLine( "Name: " + i.Name ) ; ParameterInfo[] pif = i.GetParameters () ; foreach ( ParameterInfo p in pif )

{

Console.WriteLine("Type: " + p.ParameterType + " parameter name:

" + p.Name ) ;

}

}

return 0 ;

}

public class aUsefulClass

{

public int pubInteger ; private int _privValue;

public aUsefulClass()

{

}

public aUsefulClass ( int IntegerValueIn )

{

pubInteger = IntegerValueIn ;

}

public int Add10 ( int IntegerValueIn )

{

Console.WriteLine ( IntegerValueIn ) ; return IntegerValueIn + 10 ;

}

public int TestProperty

{

get

{

return _privValue ;

}

set

{

_privValue = value ;

}

}

}

}

}

Here you have created two classes, Class1 and aUsefulClass. Class1 contains the main entry point into our application (void Main), while the other class is there for examination purposes only.

To examine the aUsefulClass, perform the following steps within the main procedure: First, declare a Type object and, using the typeof keyword, point it to the aUsefulClass. You then display the class Type and the namespace. You then use the GetConstructors to retrieve a list of the class constructs. Next, loop through this of constructors and display them on the screen. As with the constructors, use GetProperties to retrieve a list of all the properties so that you can iterate through the list and the properties out the console window.

GetMethods retrieves all the method names, as well as the methods that make up the get and set accessors of your properties. This information is then iterated and displayed at the console. You also call GetParameters to retrieve a list of the parameters for each method and display that information as well.

As you can see in Figure 32-3, your application has revealed a wealth of information about the class object.

Figure 32-3: The Reflection and Type classes reveal a great deal of information regarding the class object.

Obviously, this application isn't particularly useful, as you have the source code for the class in question and don't need Reflection to give you the details. The important thing to remember here is that Reflection works in the exact same manner even if you are dealing with an assembly for which you do not have the source code.

Generating dynamic code through Reflection

You can create code at runtime using the System.Reflection.Emit namespace. Using the classes in this namespace, an assembly can be defined in the memory, a module can be created for that assembly, new types can be defined for a module (including its members), and MSIL opcodes for the application's logic can be emitted.

Note "Opcodes" is short for operation codes. This is the actual code that is generated by the

.NET compiler.

Listing 32-5 contains the code that can be used to generate code at runtime.

Listing 32-5: Generating Code Dynamically at Runtime

using System;

using System.Reflection; using System.Reflection.Emit;

namespace DynamicCode

{

class CodeGenerator

{

Type t;

AppDomain currentDomain; AssemblyName assemName; AssemblyBuilder assemBuilder; ModuleBuilder moduleBuilder; TypeBuilder typeBuilder; MethodBuilder methodBuilder; ILGenerator msilG;

public static void Main()

{

CodeGenerator codeGen = new CodeGenerator(); Type t = codeGen.T;

if (t != null)

{

object o =

Activator.CreateInstance(t);

MethodInfo helloWorld =

t.GetMethod("HelloWorld");

if (helloWorld != null)

{

// Run the HelloWorld Method helloWorld.Invoke(o, null);

}

else

{

Console.WriteLine("Could not retrieve

MethodInfo");

}

}

else

{

Console.WriteLine("Could not access

the Type");

}

public CodeGenerator()

{

//Get the current Application Domain.

//This is needed when building code. currentDomain = AppDomain.CurrentDomain;

//Create a new Assembly

for our Methods

assemName = new AssemblyName(); assemName.Name = "BibleAssembly";

assemBuilder = currentDomain.DefineDynamicAssembly(assemName, AssemblyBuilderAccess.Run);

//Create a new module within this assembly moduleBuilder =

assemBuilder.DefineDynamicModule("BibleModule");

//Create a new type within the module

typeBuilder = moduleBuilder.DefineType("BibleClass",TypeAttributes.Public);

//Now we can add the

//HelloWorld method to the class that was just

created.

methodBuilder = typeBuilder.DefineMethod("HelloWorld", MethodAttributes.Public,null,null);

//Now we can generate some Microsoft Intermediate

//Language Code that simply writes a line of text

out

// to the console.

msilG = methodBuilder.GetILGenerator(); msilG.EmitWriteLine("Hello from C# Bible"); msilG.Emit(OpCodes.Ret);

// Create a type.

t = typeBuilder.CreateType();

}

public Type T

{

get

{

return this.t;

}

}

}

}

As expected, this application simply prints a message out to the console, as shown in Figure 32-4.

Figure 32-4: The results of the dynamically generated code

This capability of Reflection to generate objects and code at runtime is truly impressive and provides the backbone for generating fuzzy logic applications that adapt and learn as they see fit.

Note Fuzzy logic is defined as a form of algebra that uses the values of true and false to make decisions based on imprecise data. Fuzzy logic is generally attributed to artificial intelligence systems.

Summary

The Reflection and Type classes go hand in hand when you need to discover type information at runtime. These classes enable you to examine objects, load the objects dynamically at runtime, and even generate code as needed.

Chapter 33: C# Threading

In This Chapter

The multithreading power of the .NET Framework enables you to write very robust multithreaded applications in any .NET language. In this chapter, you learn the ins and outs of threading. The chapter starts with an overview of the different types of threading and how they work in the .NET Framework, and then you learn what you can do with multithreading in your own applications. As you read this chapter, carefully consider the dangers of adding multiple threads to your applications before implementing them, because multithreading is not a trivial concept.

Understanding Threading

Before you start writing multithreaded applications, you should understand what happens when threads are created, and how the operating system handles threads.

When an application executes, a primary thread is created, and the application's scope is based on this thread. An application can create additional threads to perform additional tasks. An example of creating a primary thread would be firing up Microsoft Word. The application execution starts the main thread. Within the Word application, the background printing of a document would be an example of an additional thread being created to handle another task. While you are still interacting with the main thread (the Word document), the system is carrying out your printing request. After the main application thread is killed, all other threads created as a result of that thread are also killed.

Consider these two definitions from the Microsoft Foundation Classes Software Development Kit (MFCSDK):

Process: An executing instance of an application

Thread: A path of execution within a process

C++ and the MFC have long supported the concept of developing multithreaded applications. Because the core of the Windows operating system is written using these tools, it is important that they support the capability to create threads in which tasks can be assigned and executed. In the early days of Windows 3.1, multitasking did not exist; this concept became a reality in Windows NT 3.5, and NT 4.0, and then Windows 95, 98, 98SE, ME, 2000, and XP. To take advantage of the operating system's features, multithreaded applications became more

important. Now, performing more than one task at a time is a necessary feature of an application. Visual Basic 6.0 and earlier compiled down to single-threaded applications, which meant that no matter what was going on, the VB application could only do one thing at a time.

In reality, on a single-processor system, it doesn't matter what tool you use to write your application; everything is still happening in a linear process. If you are a C++ developer, you can create new threads and perform a task while something else is going on, but it is really just sharing the same time with everything else that is running on the system. If there is only one processor, only one thing can happen at a time. This concept is called preemptive multitasking.

Understanding preemptive multitasking

Preemptive multitasking splits the processor time between running tasks, or threads. When a task is running, it is using a time slice. When the time slice has expired for the running task, somewhere around 20 milliseconds, depending on the operating system you are using, it is preempted and another task is given a time slice. The system saves the current context of the preempted task, and when the task is allocated another time slice, the context is restored and the process continues. This loop for a task continues repeatedly until the thread is aborted or the task ends. Preemptive multitasking gives the user the impression that more than one thing is happening at a time. Why do some tasks finish before others, even if you started the one that finished last first?

Understanding threading priorities and locking

When threads are created, they are assigned a priority either by the programmer or by the operating system. If an application seems to be locking up your system, it has the highest priority, and it is blocking other threads from getting any time slices. Priorities determine what happens, and in what order. Your application might be 90 percent complete with a certain process when suddenly a brand-new thread starts and races ahead of the thread that your application is currently executing, causing that thread to be reassigned to a lower priority. This frequently happens in Windows. Certain tasks take priority over others. Consider the new Windows Media Player. Starting up this process basically causes anything that is running to stop responding until it is completely loaded, including the Media Guide page.

One of the biggest dangers facing programmers writing applications that are using multiple threads are locking situations, in which two or more threads attempt to use the same resource. A thread lock occurs when a shared resource is being access by a thread and another thread with the same priority attempts to access that resource. If both threads have the same priority, and the lock is not coded correctly, the system slowly dies, because it cannot release either of the high-priority threads that are running. This can easily happen with multithreaded applications. When you assign thread priorities and are sharing global data, you must lock the context correctly in order for the operating system to handle the time slicing correctly.

Understanding symmetrical multiprocessing

On a multiprocessor system, more than one task can truly occur at the same time. Because each processor can assign time slices to tasks that are requesting work, you can perform more

Соседние файлы в предмете Программирование на C++