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

C# ПІДРУЧНИКИ / c# / Hungry Minds - C# Bible

.pdf
Скачиваний:
226
Добавлен:
12.02.2016
Размер:
4.21 Mб
Скачать

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

than one task at a time. When you need to run a processor-intensive long-running thread, such as sorting 10 million records by first name, address, Zip code, middle name, and country, using multiple processors gets the job done faster than a single processor. If you could delegate that job to another processor, then the currently running application would not be affected at all. Having more than one processor on a system enables this kind of symmetrical multiprocessing (SMP). Figure 33-1 shows the processor options for SQL Server 2000.

Figure 33-1: SQL Server 2000 Processor options dialog box

If you are running SQL Server on a multiprocessor machine, you can define the number of processors it should use for labor-intensive, long-running tasks of the sort just mentioned. SQL takes this a step further, performing queries across different processors, bringing the data together after the last thread is completed, and outputting the data to the user. This is known as thread synchronization. The main thread, which creates multiple threads, must wait for all of the threads to complete before it can continue the process.

When using an SMP system, note that a single thread still only runs on a single processor. Your single-threaded VB6 application does not perform one iota better if you throw another processor at it. Your 16-bit Access 2.0 application does not run any better either, because 16 bits still equals a single process. You need to actually create processes on the other processors in order to take advantage of them. This means that you do not design a multiprocessor GUI. You create a GUI that creates other processes and can react when those processes are completed or interrupted, while still enabling the user to use the GUI for other tasks.

Using resources: the more the merrier

Threads consume resources. When too many resources are being used, your computer is painstakingly slow. If you attempt to open 80 instances of Visual Studio .NET while installing Exchange 2000 on a computer with 96MB of RAM, you will notice that the screen does not paint correctly, the mouse doesn't move very fast, and the music you were listening to in Windows Media Player is not playing anymore. These performance problems are caused by too many threads running at the same time on an operating system with hardware that cannot handle this amount of work. If you attempt the same action on your new server, the 32processor Unisys box with 1 terabyte of RAM, you do not see any performance degradation at

Соседние файлы в папке c#