This chapter is from the book

This chapter is from the book

It is impossible to cover in one chapter, or even in one book, all of
the .NET Framework classes. Although coverage is incomplete, the .NET classes
cover a large fraction of the Win32 API, as well as much else. While a lot of
attention has been focused on the Internet-related functionality, the
development model for Windows applications has changed as well.

This chapter focuses on those classes that illustrate the key concepts and
patterns that appear throughout the .NET Framework. You will find this approach
more fruitful over the long run than our attempting to explain a little about
every class that you might need without giving you much insight. Other chapters
will go into more depth about other parts of the framework, such as Windows
Forms, ASP.NET, ADO.NET security, and Web Services.

We start out by exploring the concept of reflection and metadata. Metadata
appears everywhere in .NET and is critical to understanding how the Common
Language Runtime (CLR) can provide services for your applications. Next, we
explore file input/output for several reasons. First, it introduces the
important topic of serialization. Second, the Path class provides an
example of how some framework classes provide some or all of their functionality
through static methods. Third, the formatter classes are used in several places
in .NET.

Understanding serialization will give you a concrete idea of how the
framework can handle objects transparently for you. It also appears in a
supporting role any place where objects have to be stored or transported. Our
discussion of the ISerializable interface again demonstrates how much
easier it is to implement an interface in .NET than with COM.

To further develop an understanding of the .NET model for applications, we
introduce programming with threads under .NET and several .NET synchronization
techniques to handle multithreading conflicts. The various synchronization
techniques illustrate the tradeoffs of using attributes supplied by the
framework versus doing it your self.

To further your understanding of the .NET programming model, we introduce
context and the use of proxies and stubs to implement system services. We
also look at using application domains, which are more efficient than
Win32 processes in achieving application isolation.

The asynchronous design pattern appears throughout .NET and is discussed in
some detail. We give some examples of remoting because it is a key technology
and it summarizes many of the concepts developed in this chapter. The chapter
uses several attributes provided by the .NET Framework, and we show how to implement
and use custom attributes. We discuss finalization so that you can understand
how to make sure resources are properly freed in your applications.

Metadata and Reflection

The Serialization example in Chapter 2 demonstrates how metadata makes
many of the services of the CLR possible. Many of the technologies we cover in
the rest of the book rely on metadata, although we will not always stop and
point this out.

Metadata is information about the assemblies, modules, and types that
constitute .NET programs. If you have ever had to create IDL to generate a type
library so that your C++ COM objects could be called by Visual Basic, or to
create proxies and stubs, you will appreciate how useful metadata is and will be
grateful that it comes "for free."

Compilers emit metadata, and the CLR, the .NET Framework, or your own
programs can use it. Since we want to give you an understanding of how metadata
works, we will focus our discussion on the use of metadata, not the creation of
metadata. Metadata is read using classes in the System::Reflection
namespace.1

When you load an assembly and its associated modules and types, the metadata
is loaded along with it. You can then query the assembly to get those associated
types. You can also call GetType on any CLR type and get its metadata.
GetType is a method on System::Object, which every CLR type
inherits from. After you get the Type associated with an object, you can
use the reflection methods to get the related metadata.

The Reflection sample program takes the case study's
Customer assembly and prints out some of the metadata available. You
should examine the output and source code as you read the next sections. You
should especially compare the output of the program with the source code in the
file customer.h.

The program clearly shows that it is possible to retrieve all of the types in
an assembly and reconstruct the structures, interfaces, properties, events, and
methods associated with those types.

First we load the assembly into memory and write out its name.

Assembly *a = Assembly::Load(assemblyName);
Console::WriteLine(

"Assembly {0} found.", a->FullName);

The output for this statement is appropriate for an unsigned assembly:

One of the properties of the Assembly class is the CodeBase,
discussed Chapter 7, "Assemblies and Deployment." The security
evidence associated with this assembly is another property.

The following code tries to get the entry point for the assembly:

MethodInfo *entryMethodInfo = a->EntryPoint;

Since this is a typical C++ component assembly, the entry point is
__DllMainCRTStartup@12. If this was an executable program, we could use
the Invoke method on the MethodInfo class to run the startup code
in the assembly.2

The sample uses the assembly's GetModules method to find
associated modules with this assembly. In this case we have only one, named
customer.dll. We could then find the types associated with the module.
Instead, we use the assembly's GetTypes method to return an array of
the assembly's types.

Type

The abstract class Type in the System namespace defines .NET
types. Since there are no functions outside of classes or global variables in
.NET, getting all the types in an assembly will allow us to get all the metadata
about the code in that assembly. Type represents all the types present in
.NET: classes, structs, interfaces, values, arrays, and enumerations.

The Type class is also returned by the GetType method on the
System::Object class and the static GetType method on the
Type class itself. The latter method can be used only with types that can
be resolved statically.

One of Type's properties is the assembly to which it belongs. You
can get all the types in the containing assembly once you have the Type
of one object. Type is an abstract class, and at runtime an instance of
System::RuntimeType is returned.

If you examine the program's output, you will see that each type in the
assembly, CustomerListItem, ICustomer, Customer, and
Customers, is found and its metadata is printed out. We can find out the
standard attributes and the type from which the class derives for each type
through the Attributes and BaseType properties.

The methods associated with the Type class enable you to get the
associated fields, properties, interfaces, events, and methods. For example, the
Customer type has no interfaces, properties, or events, but it has four
fields, three constructors, and the methods inherited from its BaseTypeSystem::Object:

These were obtained with the GetInterfaces, GetFields,
GetProperties, GetEvents, GetConstructors, and
GetMethods methods on the Type class. Since an interface is a
type, GetInterfaces returns an array of Types representing the
interfaces inherited or implemented by the Type queried. Since fields,
properties, events, and methods are not types, their accessor methods do not
return Types. Each of their accessor methods returns an appropriate
class: FieldInfo, PropertyInfo, EventInfo,
ConstructorInfo, and MethodInfo. All these classes, as well as the
Type class, inherit from the MemberInfo class that is the abstract
base class for member metadata.

Let us examine some of the metadata associated with a class method. Using the
reflection methods, we were able to reconstruct the signatures for all the
classes and interfaces in the Customer assembly. Here is the output for
the methods of the Customers class:

Except that a constructor does not have a return type, the exact same code
reconstitutes the calling sequences for the class's constructors.

The MethodInfo class has properties that help us determine if the
method is static, public, protected, internal, or private as well as determine
the return type and method name. The method parameters are stored in a property
array of type ParameterInfo class.

This example should also make it clear that types are assembly relative. The
same type name and layout in two different assemblies are treated by the runtime
as two separate types. When versioning assemblies, you have to be careful when
mixing versioned types or the same types in two different assemblies.

All this metadata allows the CLR and the framework to provide services to
your applications, because they can understand the structure of your types.

Late Binding

Reflection can also be used to implement late binding. Late binding is where
the method to be called is determined during execution rather than at
compilation time. It is one example of how metadata can be used to provide
functionality. As the previous example demonstrates, you can extract the
signature of a method associated with a type. The MethodInfo object has
all the needed metadata for a class method. The Dynamic sample
demonstrates a very simple example of late
binding.3

We dynamically load an assembly and get the metadata for a method of a
particular type:

One thing that C++ programmers need to remember when doing reflection
programming is that when you deal with strings that contain namespaces or
classes, you must use properly formatted strings that are understood by the
reflection class methods. So, the fully qualified class name in the code above
is OI.NetCpp.Acme.Customers rather than the C++ style format
OI::NetCpp::Acme::Customers. Thus, the format used is like that of C#, not
C++.

Using the reflection classes, we could have made this completely dynamic by
arbitrarily picking types, methods, and constructors from the Customer
assembly using the techniques of the last example, but we wanted to keep the
Dynamic example simple. A more ambitious program could do something much
more interesting, such as implement an assembly decompiler that generates
Managed C++, C#, or VB.NET source code directly from a compiled assembly.

The System namespace has an Activator class that has overloaded
CreateInstance methods to create an instance of any .NET type using the
appropriate constructor. The Activator class is discussed in this
chapter's section on remoting. We invoke a constructor with no arguments to
create an instance of the Customers object.

Again, note the use of the single periods rather than double colons in the
string System.Collections.ArrayList.

This code did not use any specific objects or types from the Customer
assembly. We did use some knowledge about the assembly to keep the code simple
to illustrate the main points. It should be clear, however, how to make this
completely general.

You can take this one step further and use the classes that emit metadata(in System::Reflection::Emit). You can even dynamically create an
assembly in memory, and then load and run it!