【MSDN文摘】What You Need to Know to Move from C++ to C#

【打不死的猫注:
本文中涉及的测试程序可从如下地址下载:
http://download.microsoft.com/download/2/8/c/28c4ace3-f5ed-4e14-bc64-3d563b807dfb/CtoCsharp.exe
本文转载自MSDN,原文由鼎鼎大名的Jesse Liberty撰写,原文地址是:
http://msdn.microsoft.com/msdnmag/issues/01/07/ctocsharp/default.aspx

What You Need to Know to Move from C++ to C#

Jesse Liberty

This article assumes you're familiar with C++

SUMMARY
 C# builds on the syntax and semantics of C++, allowing C programmers to take advantage of .NET and the common language runtime. While the transition from C++ to C# should be a smooth one, there are a few things to watch out for including changes to new, structs, constructors, and destructors. This article explores the language features that are new to C# such as garbage collection, properties, foreach loops, and interfaces. Following a discussion of interfaces, there's a discussion of properties, arrays, and the base class libraries. The article concludes with an exploration of asynchronous I/O, attributes and reflection, type discovery, and dynamic invocation.


 

E very 10 years or so, developers must devote time and energy to learning a new set of programming skills. In the early 1980s it was Unix and C; in the early 1990s it was Windows® and C++; and today it is the Microsoft® .NET Framework and C#. While this process takes work, the benefits far outweigh the costs. The good news is that with C# and .NET the analysis and design phases of most projects are virtually unchanged from what they were with C++ and Windows. That said, there are significant differences in how you will approach programming in the new environment. In this article I'll provide information about how to make the leap from programming in C++ to programming in C#.
Many articles (for example, Sharp New Language: C# Offers the Power of C++ and Simplicity of Visual Basic) have explained the overall improvements that C# implements, and I won't repeat that information here. Instead, I'll focus on what I see as the most significant change when moving from C++ to C#: going from an unmanaged to a managed environment. I'll also warn you about a few significant traps awaiting the unwary C++ programmer and I'll show some of the new features of the language that will affect how you implement your programs.

Moving to a Managed Environment
C++ was designed to be a low-level platform-neutral object-oriented programming language. C# was designed to be a somewhat higher-level component-oriented language. The move to a managed environment represents a sea change in the way you think about programming. C# is about letting go of precise control, and letting the framework help you focus on the big picture.
For example, in C++ you have tremendous control over the creation and even the layout of your objects. You can create an object on the stack, on the heap, or even in a particular place in memory using the placement operator new.
With the managed environment of .NET, you give up that level of control. When you choose the type of your object, the choice of where the object will be created is implicit. Simple types (ints, doubles, and longs) are always created on the stack (unless they are contained within other objects), and classes are always created on the heap. You cannot control where on the heap an object is created, you can't get its address, and you can't pin it down in a particular memory location. (There are ways around these restrictions, but they take you out of the mainstream.)
You no longer truly control the lifetime of your object. C# has no destructor. The garbage collector will take your item's storage back sometime after there are no longer any references to it, but finalization is nondeterministic.
The very structure of C# reflects the underlying framework. There is no multiple inheritance and there are no templates because multiple inheritance is terribly difficult to implement efficiently in a managed, garbage-collected environment, and because generics have not been implemented in the framework.
The C# simple types are nothing more than a mapping to the underlying common language runtime (CLR) types. For example, a C# int maps to a System.Int32. The types in C# are determined not by the language, but by the common type system. In fact, if you want to preserve the ability to derive C# objects from Visual Basic® objects, you must restrict yourself further, to the common language subset—those features shared by all .NET languages.
On the other hand, the managed environment and CLR bring a number of tangible benefits. In addition to garbage collection and a uniform type system across all .NET languages, you get a greatly enhanced component-based language, which fully supports versioning and provides extensible metadata, available at runtime through reflection. There is no need for special support for late binding; type discovery and late binding are built into the language. In C#, enums and properties are first-class members of the language, fully supported by the underlying engine, as are events and delegates (type-safe function pointers).
The key benefit of the managed environment, however, is the .NET Framework. While the framework is available to any .NET language, C# is a language that's well-designed for programming with the framework's rich set of classes, interfaces, and objects.

Traps
C# looks a lot like C++, and while this makes the transition easy, there are some traps along the way. If you write what looks like perfectly legitimate code in C++, it won't compile, or worse, it won't behave as expected. Most of the syntactic changes from C++ to C# are trivial (no semicolon after a class declaration, Main is now capitalized). I'm building a Web page which lists these for easy reference, but most of these are easily caught by the compiler and I won't devote space to them here. I do want to point out a few significant changes that will cause problems, however.

Reference and Value Types
C# distinguishes between value types and reference types. Simple types (int, long, double, and so on) and structs are value types, while all classes are reference types, as are Objects. Value types hold their value on the stack, like variables in C++, unless they are embedded within a reference type. Reference type variables sit on the stack, but they hold the address of an object on the heap, much like pointers in C++. Value types are passed to methods by value (a copy is made), while reference types are effectively passed by reference.

Structs
Structs are significantly different in C#. In C++ a struct is exactly like a class, except that the default inheritance and default access are public rather than private. In C# structs are very different from classes. Structs in C# are designed to encapsulate lightweight objects. They are value types (not reference types), so they're passed by value. In addition, they have limitations that do not apply to classes. For example, they are sealed, which means they cannot be derived from or have any base class other than System.ValueType, which is derived from Object. Structs cannot declare a default (parameterless) constructor.
On the other hand, structs are more efficient than classes so they're perfect for the creation of lightweight objects. If you don't mind that the struct is sealed and you don't mind value semantics, using a struct may be preferable to using a class, especially for very small objects.

Everything Derives from Object
In C# everything ultimately derives from Object. This includes classes you create, as well as value types such as int or structs. The Object class offers useful methods, such as ToString. An example of when you use ToString is with the System.Console.WriteLine method, which is the C# equivalent of cout. The method is overloaded to take a string and an array of objects.
To use WriteLine you provide substitution parameters, not unlike the old-fashioned printf. Assume for a moment that myEmployee is an instance of a user-defined Employee class and myCounter is an instance of a user-defined Counter class. If you write the following code
   

Console.WriteLine( " The employee: {0}, the counter value: {1} " , myEmployee, myCounter);  

    WriteLine will call the virtual method Object.ToString on each of the objects, substituting the strings they return for the parameters. If the Employee class does not override ToString, the default implementation (derived from System.Object) will be called, which will return the name of the class as a string. Counter might override ToString to return an integer value. If so, the output might be:
 
 
The employee: Employee, the counter value:  12
What happens if you pass integer values to WriteLine? You can't call ToString on an integer, but the compiler will implicitly box the int in an instance of Object whose value will be set to the value of the integer. When WriteLine calls ToString, the object will return the string representation of the integer's value (see Figure 1).

Reference Parameters and Out Parameters
In C#, as in C++, a method can only have one return value. You overcome this in C++ by passing pointers or references as parameters. The called method changes the parameters, and the new values are available to the calling method.
When you pass a reference into a method, you do have access to the original object in exactly the way that passing a reference or pointer provides you access in C++. With value types, however, this does not work. If you want to pass the value type by reference, you mark the value type parameter with the ref keyword.
 
 
None.gif public   void  GetStats( ref   int  age,  ref   int  ID,  ref   int  yearsServed)
Note that you need to use the ref keyword in both the method declaration and the actual call to the method.
 
 
Fred.GetStats( ref  age,  ref  ID,  ref  yearsServed);
You can now declare age, ID, and yearsServed in the calling method and pass them into GetStats and get back the changed values.
C# requires definite assignment, which means that the local variables, age, ID, and yearsServed must be initialized before you call GetStats. This is unnecessarily cumbersome; you're just using them to get values out of GetStats. To address this problem, C# also provides the out keyword, which indicates that you may pass in uninitialized variables and they will be passed by reference. This is a way of stating your intentions explicitly:
 
 
public   void  GetStats( out   int  age,  out   int  ID,  out   int  yearsServed)
Again, the calling method must match.
 
 
Fred.GetStats( out  age, out  ID,  out  yearsServed);

Calling New
In C++, the new keyword instantiates an object on the heap. Not so in C#. With reference types, the new keyword does instantiate objects on the heap, but with value types such as structs, the object is created on the stack and a constructor is called.
You can, in fact, create a struct on the stack without using new, but be careful! New initializes the object. If you don't use new, you must initialize all the values in the struct by hand before you use it (before you pass it to a method) or it won't compile. Once again, definite assignment requires that every object be initialized (see Figure 2).

Properties
Most C++ programmers try to keep member variables private. This data hiding promotes encapsulation and allows you to change your implementation of the class without breaking the interface your clients rely on. You typically want to allow the client to get and possibly set the value of these members, however, so C++ programmers create accessor methods whose job is to modify the value of the private member variables.
In C#, properties are first-class members of classes. To the client, a property looks like a member variable, but to the implementor of the class it looks like a method. This arrangement is perfect; it allows you total encapsulation and data hiding while giving your clients easy access to the members.
You can provide your Employee class with an Age property to allow clients to get and set the employee's age member.
  
None.gif public   int  Age
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    
get
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        
return age;
ExpandedSubBlockEnd.gif    }

InBlock.gif    
set
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        age 
= value;
ExpandedSubBlockEnd.gif    }

ExpandedBlockEnd.gif}

    The keyword value is implicitly available to the property. If you write
 
 
Fred.Age  =   17 ;
the compiler will pass in the value 17 as value.
You can create a read-only property for YearsServed by implementing the Get and not the Set accessor.
 
 
None.gif public   int  YearsServed
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    
get
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        
return yearsServed;
ExpandedSubBlockEnd.gif    }

ExpandedBlockEnd.gif}
If you change your driver program to use these accessors, you can see how they work (see Figure 3).
You can get Fred's age through the property and then you can use that property to set the age. You can access the YearsServed property to obtain the value, but not to set it; if you uncomment the last line, the program will not compile.
If you decide later to retrieve the Employee's age from a database, you need change only the accessor implementation; the client will not be affected.

Arrays
C# provides an array class which is a smarter version of the traditional C/C++ array. For example, it is not possible to write past the bounds of a C# array. In addition, Array has an even smarter cousin, ArrayList, which can grow dynamically to manage the changing size requirements of your program.
Arrays in C# come in three flavors: single-dimensional, multidimensional rectangular arrays (like the C++ multidimensional arrays), and jagged arrays (arrays of arrays).
You can create a single-dimensional array like this:
 
 
int [] myIntArray  =   new   int [ 5 ];
Otherwise, you can initialize it like this:
 
 
int [] myIntArray  =  {  2 4 6 8 10  };
You can create a 4×3 rectangular array like this:
 
 
int [,] myRectangularArray  =   new   int [rows, columns];
Alternatively, you can simply initialize it, like this:
 
 
None.gif int [,] myRectangularArray  =   
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{0,1,2}dot.gif{3,4,5}dot.gif{6,7,8}dot.gif{9,10,11}
ExpandedBlockEnd.gif}
;
Since jagged arrays are arrays of arrays, you supply only one dimension
 
 
int [][] myJaggedArray  =   new   int [ 4 ][];
and then create each of the internal arrays, like so:
 
 
None.gif myJaggedArray[ 0 =   new   int [ 5 ]; 
None.gifmyJaggedArray[
1 =   new   int [ 2 ]; 
None.gifmyJaggedArray[
2 =   new   int [ 3 ]; 
None.gifmyJaggedArray[
3 =   new   int [ 5 ];
Because arrays derive from the System.Array object, they come with a number of useful methods, including Sort and Reverse.

Indexers
It is possible to create your own array-like objects. For example, you might create a listbox which has a set of strings that it will display. It would be convenient to be able to access the contents of the box with an index, just as if it were an array.
 
 
None.gif string  theFirstString  =  myListBox[ 0 ];
None.gif
string  theLastString  =  myListBox[Length - 1 ];
This is accomplished with Indexers. An Indexer is much like a property, but supports the syntax of the index operator. Figure 4 shows a property whose name is followed by the index operator.
Figure 5 shows how to implement a very simple ListBox class and provide indexing for it.

Interfaces
A software interface is a contract for how two types will interact. When a type publishes an interface, it tells any potential client, "I guarantee I'll support the following methods, properties, events, and indexers."
C# is an object-oriented language, so these contracts are encapsulated in entities called interfaces. The interface keyword declares a reference type which encapsulates a contract.
Conceptually, an interface is similar to an abstract class. The difference is that an abstract class serves as the base class for a family of derived classes, while interfaces are meant to be mixed in with other inheritance trees.

The IEnumerable Interface
Returning to the previous example, it would be nice to be able to print the strings from the ListBoxTest class using a foreach loop, as you can with a normal array. You can accomplish this by implementing the IEnumerable interface in your class, which is used implicitly by the foreach construct. IEnumerable is implemented in any class that wants to support enumeration and foreach loops.
IEnumerable has only one method, GetEnumerator, whose job is to return a specialized implementation of IEnumerator. Thus the semantics of an Enumerable class allow it to provide an Enumerator.
The Enumerator must implement the IEnumerator methods. This can be implemented either directly by the container class or by a separate class. The latter approach is generally preferred because it encapsulates this responsibility in the Enumerator class rather than cluttering up the container.
I'll add an Enumerator to the ListBoxTest that you have already seen in Figure 5. Because the Enumerator class is specific to my container class (that is, because ListBoxEnumerator must know a lot about ListBoxTest) I will make it a private implementation, contained within ListBoxTest.
In this version, ListBoxTest is defined to implement the IEnumerable interface. The IEnumerable interface must return an Enumerator.
 
 
None.gif public  IEnumerator GetEnumerator()
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    
return (IEnumerator) new ListBoxEnumerator(this);
ExpandedBlockEnd.gif}
Notice that the method passes the current ListBoxTest object (this) to the enumerator. That will allow the enumerator to enumerate this particular ListBoxTest object.
The class to implement the Enumerator is implemented here as ListBoxEnumerator, which is a private class defined within ListBoxTest. Its work is fairly straightforward.
The ListBoxTest to be enumerated is passed in as an argument to the constructor, where it is assigned to the member variable myLBT. The constructor also sets the member variable index to -1, indicating that enumerating the object has not yet begun.
 
 
None.gif public  ListBoxEnumerator(ListBoxTest theLB)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    myLBT 
= theLB;
InBlock.gif    index 
= -1;
ExpandedBlockEnd.gif}
The MoveNext method increments the index and then checks to ensure that you have not run past the end of the object you're enumerating. If you have, you return false; otherwise, true is returned.
 
 
None.gif public   bool  MoveNext()
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    index
++;
InBlock.gif    
if (index >= myLBT.myStrings.Length)
InBlock.gif        
return false;
InBlock.gif    
else
InBlock.gif        
return true;
ExpandedBlockEnd.gif}
Reset does nothing but reset the index to -1.
The property Current is implemented to return the last string added. This is an arbitrary decision; in other classes Current will have whatever meaning the designer decides is appropriate. However it's defined, every enumerator must be able to return the current member, as accessing the current member is what enumerators are for.
 
 
None.gif public   object  Current
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    
get
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        
return(myLBT[index]);
ExpandedSubBlockEnd.gif    }

ExpandedBlockEnd.gif}
That's all there is to it. The call to foreach fetches the enumerator and uses it to enumerate over the array. Since foreach will display every string whether or not you've added a meaningful value, I've changed the initialization of myStrings to eight items to keep the display manageable.
 
 
myStrings  =   new  String[ 8 ];

Using the Base Class Libraries
To get a better sense of how C# differs from C++ and how your approach to solving problems might change, let's examine a slightly less trivial example. I'll build a class to read a large text file and display its contents on the screen. I'd like to make this a multithreaded program so that while the data is being read from the disk, I can do other work.
In C++ you would create a thread to read the file, and another thread to do the other work. These threads would work independently, but they might need synchronization. You can do all of that in C# as well, but most of the time you won't need to write your own threading because .NET provides very powerful mechanisms for asynchronous I/O.
The asynchronous I/O support is built into the CLR and is nearly as easy to use as the normal I/O stream classes. You start by informing the compiler that you'll be using objects from a number of System namespaces:
 
 
None.gif using  System;
None.gif
using  System.IO;
None.gif
using  System.Text;
When you include System, you do not automatically include all its subsidiary namespaces, each must be explicitly included with the using keyword. Since you'll be using the I/O stream classes, you'll need System.IO, and you want System.Text to support ASCII encoding of your byte stream, as you'll see shortly.
The steps involved in writing this program are surprisingly simple because .NET will do most of the work for you. I'll use the BeginRead method of the Stream class. This method provides asynchronous I/O, reading in a buffer full of data, and then calling your callback method when the buffer is ready for you to process.
You need to pass in a byte array as the buffer and a delegate for the callback method. You'll declare both of these as private member variables of your driver class.
 
 
None.gif public   class  AsynchIOTester
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    
private Stream inputStream;       
InBlock.gif    
private byte[] buffer;          
InBlock.gif    
private AsyncCallback myCallBack;
The member variable inputStream is of type Stream, and it is on this object that you will call the BeginRead method, passing in the buffer as well as the delegate (myCallBack). A delegate is very much like a type-safe pointer to member function. In C#, delegates are first-class elements of the language.
.NET will call your delegated method when the byte has been filled from the file on disk so that you can process the data. While you're waiting you can do other work (in this case, incrementing an integer from 1 to 50,000, but in a production program you might be interacting with the user or doing other useful tasks).
The delegate in this case is declared to be of type AsyncCallback, which is what the BeginRead method of Stream expects. An AsyncCallback delegate is declared in the System namespace as follows:
 
 
None.gif public   delegate   void  AsyncCallback (IAsyncResult ar);
Thus, this delegate may be associated with any method that returns void and takes an IAsyncResult interface as a parameter. The CLR will pass in the IAsyncResult interface object at runtime when the method is called; you only have to declare the method

None.gif void  OnCompletedRead(IAsyncResult asyncResult)

and then to hook up the delegate in the constructor:
 
None.gif AsynchIOTester()
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif      •••
InBlock.gif     myCallBack 
= new AsyncCallback(this.OnCompletedRead);
ExpandedBlockEnd.gif}

    This assigns to the member variable myCallback (which was previously defined to be of type AsyncCallback) the instance of the delegate created by calling the AsyncCallback constructor and passing in the method you want to associate with the delegate.
Here's how the entire program works, step by step. In Main you create an instance of the class and tell it to run:
 
 
None.gif public   static   void  Main()
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    AsynchIOTester theApp 
= new AsynchIOTester();    
InBlock.gif    theApp.Run();
ExpandedBlockEnd.gif}
The call to new fires up the constructor. In the constructor you open a file and get a Stream object back. You then allocate space in the buffer and hook up the callback mechanism.
 
 
None.gif AsynchIOTester()
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    inputStream 
= File.OpenRead(@"C:\MSDN\fromCppToCS.txt");
InBlock.gif    buffer 
= new byte[BUFFER_SIZE];
InBlock.gif    myCallBack 
= new AsyncCallback(this.OnCompletedRead);
ExpandedBlockEnd.gif}
In the Run method, you call BeginRead, which will cause an asynchronous read of the file.
 
 
None.gif inputStream.BeginRead(
None.gif     buffer,             
//  where to put the results
None.gif
      0 ,                   //  offset
None.gif
     buffer.Length,       //  how many bytes (BUFFER_SIZE)
None.gif
     myCallBack,          //  call back delegate
None.gif
      null );               //  local state object
You then go on to do other work.
 
 
None.gif for  ( long  i  =   0 ; i  <   50000 ; i ++ )        
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    
if (i%1000 == 0)
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        Console.WriteLine(
"i: {0}", i);
ExpandedSubBlockEnd.gif    }

ExpandedBlockEnd.gif}
When the read completes, the CLR will call the callback method.
 
 
None.gif void  OnCompletedRead(IAsyncResult asyncResult)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
The first thing you do in OnCompletedRead is find out how many bytes were read by calling the EndRead method of the Stream object, passing in the IAsyncResult interface object passed in by the common language runtime.
 
 
int  bytesRead  =  inputStream.EndRead(asyncResult);
The result of this call to EndRead is to get back the number of bytes read. If the number is greater than zero, convert the buffer into a string and write it to the console, then call BeginRead again for another asynchronous read.
 
 
None.gif if  (bytesRead  >   0 )
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    String s 
= Encoding.ASCII.GetString(buffer, 0, bytesRead);
InBlock.gif    Console.WriteLine(s);
InBlock.gif    inputStream.BeginRead(buffer, 
0, buffer.Length,
InBlock.gif                          myCallBack, 
null);
ExpandedBlockEnd.gif}
Now you can do other work (in this case, counting to 50,000) while the reads are taking place, but you can handle the read data (in this case, by outputting it to the console) each time a buffer is full. The complete source code for this example, AsynchIO.cs, is available for download from the link at the top of this article.
Management of the asynchronous I/O is provided entirely by the CLR. It gets even nicer when you read over the network.

Reading a File Across the Network
In C++, reading a file across the network is a nontrivial programming exercise. .NET provides extensive support for this. In fact, reading files across the network is just another use of the standard Base Class Library Stream classes.
Start by creating an instance of the TCPListener class, to listen to a TCP/IP port (port 65000 in this case).
 
 
TCPListener tcpListener  =   new  TCPListener( 65000 );
Once constructed, ask the TCPListener object to start listening.
 
 
tcpListener.Start();
Now wait for a client to request a connection.
 
 
Socket socketForClient  =  tcpListener.Accept();
The Accept method of the TCPListener object returns a Socket object, which represents a standard Berkeley socket interface and which is bound to a specific end point (in this case, the client). Accept is a synchronous method and will not return until it receives a connection request. If the socket is connected, you're ready to send the file to the client.
 
 
None.gif if  (socketForClient.Connected)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif•••
Next you have to create a NetworkStream class, passing the socket in to the constructor.
 
 
None.gif NetworkStream networkStream  =   new  NetworkStream(socketForClient);
Then create a StreamWriter object much as you did before, except this time not on a file, but on the NetworkStream you just created.
 
 
None.gif System.IO.StreamWriter streamWriter  =   new  System.IO.StreamWriter(networkStream);
When you write to this stream, the stream is sent over the network to the client. The complete source code, TCPServer.cs, is also available for download.

Creating the Client
The client instantiates a TCPClient class, which represents a TCP/IP client connection to a host.
 
 
None.gif TCPClient socketForServer;
None.gifsocketForServer 
=   new  TCPClient( " localHost " 65000 );
With this TCPClient, you can create a NetworkStream, and on that stream create a StreamReader.
 
 
None.gif NetworkStream networkStream  =  socketForServer.GetStream();
None.gifSystem.IO.StreamReader streamReader 
=   new  System.IO.StreamReader(networkStream);
Now, read the stream as long as there is data on it, and output the results to the console.
 
 
None.gif do
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    outputString 
= streamReader.ReadLine();
InBlock.gif
InBlock.gif    
if( outputString != null )
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        Console.WriteLine(outputString);
ExpandedSubBlockEnd.gif    }

ExpandedBlockEnd.gif}

None.gif
while ( outputString  !=   null  );
To test this, you create a simple test file:
 
 
None.gif This  is  line one
None.gifThis 
is  line two
None.gifThis 
is  line three
None.gifThis 
is  line four
Here is the output from the server:
 
 
None.gif Output (Server)
None.gifClient connected
None.gifSending This 
is  line one
None.gifSending This 
is  line two
None.gifSending This 
is  line three
None.gifSending This 
is  line four
None.gifDisconnecting from clientdot.gif
None.gifExitingdot.gif
And here is the output from the client:
    
 
 
None.gif This  is  line one
None.gifThis 
is  line two
None.gifThis 
is  line three
None.gifThis 
is  line four

Attributes and Metadata
One significant difference between C# and C++ is that C# provides inherent support for metadata: data about your classes, objects, methods, and so forth. Attributes come in two flavors: those that are supplied as part of the CLR and attributes you create for your own purposes. CLR attributes are used to support serialization, marshaling, and COM interoperability. A search of the CLR reveals a great many attributes. As you've seen, some attributes are applied to an assembly, others to a class or interface. These are called the attribute targets.
Attributes are applied to their target by placing them in square brackets immediately before the target item. Attributes may be combined, either by stacking one on top of another
[assembly: AssemblyDelaySign(false)]
[assembly: AssemblyKeyFile(".\\keyFile.snk")]
or by separating the attributes with commas.
[assembly: AssemblyDelaySign(false),
assembly: AssemblyKeyFile(".\\keyFile.snk")]


Custom Attributes
You are free to create your own custom attributes and to use them at runtime as you see fit. For example, you might create a documentation attribute to tag sections of code with the URL of associated documentation. Or you might tag your code with code review comments or bug fix comments.
Suppose your development organization wants to keep track of bug fixes. It turns out you keep a database of all your bugs, but you'd like to tie your bug reports to specific fixes in the code. You might add comments to your code similar to the following:

// Bug 323 fixed by Jesse Liberty 1/1/2005.    
This would make it easy to see in your source code, but it would be nice if you could extract this information into a report or keep it in a database so that you could search for it. It would also be nice if all the bug report notations used the same syntax. A custom attribute may be just what you need. You would then replace your comment with something like this:
[BugFix(323,"Jesse Liberty","1/1/2005") Comment="Off by one error"]   
Attributes, like most things in C#, are classes. To create a custom attribute, you derive your new custom attribute class from System.Attribute.
public class BugFixAttribute : System.Attribute   
You need to tell the compiler what kinds of elements this attribute can be used with (the attribute target). You specify this with (what else?) an attribute.
[AttributeUsage(AttributeTargets.ClassMembers, AllowMultiple = true)]   
AttributeUsage is an attribute applied to attributes—a meta attribute. It provides, if you will, meta-metadata; that is data about the metadata. In this case, you pass two arguments: the first is the target (in this case class members) and a flag indicating whether a given element may receive more than one such attribute. AllowMultiple has been set to true, which means a class member may have more than one BugFixAttribute assigned.
If you wanted to combine Attribute targets, you can OR them together.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface,
AllowMultiple = true)]    
This would allow the attribute to be attached to either a Class or an Interface.
The new custom attribute is named BugFixAttribute. The convention is to append the word Attribute to your attribute name. The compiler supports this by allowing you to call the attribute, when you assign it to an element, with the shorter version of the name. Thus, you can write this:
[BugFix(123, "Jesse Liberty", "01/01/05", Comment="Off by one")]   
The compiler will first look for an attribute named BugFix and, not finding that, will then look for BugFixAttribute.
Every attribute must have at least one constructor. Attributes take two types of parameters, positional and named. In the previous example, the bug ID, the programmer's name, and the date were positional parameters and comment was a named parameter. Positional parameters are passed in through the constructor and must be passed in the order declared in the constructor.
public BugFixAttribute(int bugID, string programmer, string date)
{
this.bugID = bugID;
this.programmer = programmer;
this.date = date;
}
Named parameters are implemented as properties.


Using the Attribute
To test the attribute, create a simple class named MyMath and give it two functions. Then assign the bug fix attributes to the class.
[BugFixAttribute(121,"Jesse Liberty","01/03/05")]
[BugFixAttribute(107,"Jesse Liberty","01/04/05",
Comment="Fixed off by one errors")]
public class MyMath    
These attributes will be stored with the metadata. Figure 6 provides the complete source code. The following shows the output:
Calling DoFunc(7). Result: 9.3333333333333339    
As you can see, the attributes had absolutely no impact on the output, and creating attributes has no impact on performance. In fact, for the moment, you have only my word that the attributes exist at all. A quick look at the metadata using ILDASM does reveal that the attributes are in place, however, as shown in Figure 7.

Reflection
For this to be useful, you need a way to access the attributes from the metadata—ideally during runtime. C# provides support for reflection for examining the metadata. Start by initializing an object of type MemberInfo. This object, in the System.Reflection namespace, is provided to discover the attributes of a member and to provide access to the metadata.
System.Reflection.MemberInfo inf = typeof(MyMath);  
Call the typeof operator on the MyMath type, which returns an object of type Type, which derives from MemberInfo.
The next step is to call GetCustomAttributes on this MemberInfo object, passing in the type of the attribute you want to find. What you get back is an array of objects, each of which is of type BugFixAttribute:
object[] attributes;
attributes = Attribute.GetCustomAttributes(inf,
typeof(BugFixAttribute));    
You can now iterate through this array, printing out the properties of the BugFixAttribute object, as shown in Figure 8. When this replacement code is put into the listing in Figure 6, the metadata is displayed.

Type Discovery
You can use reflection to explore and examine the contents of an assembly. You'll find this particularly useful if you're building a tool which needs to display information about the assembly, or if you want to dynamically invoke methods in the assembly. You might want to do so if you're developing a scripting engine, which would allow your users to generate scripts and run them through your program.
With reflection, you can find the types associated with a module, the methods, fields, properties, and events associated with a type, as well as the signatures of each of the type's methods, the interfaces supported by the type, and the type's superclass.
To start, let's load an assembly dynamically with the Assembly.Load static method. The signature for this method is as follows:
public static Assembly.Load(AssemblyName)    
Then you should pass in the core library.
Assembly a = Assembly.Load("Mscorlib.dll");    
Once the assembly is loaded, you can call GetTypes to return an array of Type objects. The Type object is the heart of reflection. Type represents type declarations: classes, interfaces, arrays, values, and enumerations.
Type[] types = a.GetTypes();    
The assembly returns an array of types that can be displayed in a foreach loop. The output from this will fill many pages. Here is a short excerpt from the output:
Type is System.TypeCode
Type is System.Security.Util.StringExpressionSet
Type is System.Text.UTF7Encoding$Encoder
Type is System.ArgIterator
Type is System.Runtime.Remoting.JITLookupTable
1205 types found    
You have obtained an array filled with the types from the core library, and printed them one by one. As the output shows, the array contains 1,205 entries.

Reflecting on a Type
You can reflect on a single type in the assembly as well. To do so, you extract a type from the assembly with the GetType method.
public class Tester
{
public static void Main()
{
// examine a single object
Type theType = Type.GetType("System.Reflection.Assembly");
Console.WriteLine("\nSingle Type is {0}\n", theType);
}
}
The output looks like this:
Single Type is System.Reflection.Assembly

Finding the Members
You can ask this type for all its members, listing all the methods, properties, and fields, as you can see in Figure 9.
Once again the output is quite lengthy, but within the output you see fields, methods, constructors, and properties, as shown in this excerpt:
System.String s_localFilePrefix is a Field
Boolean IsDefined(System.Type) is a Method
Void .ctor() is a Constructor
System.String CodeBase  is a Property
System.String CopiedCodeBase  is a Property

Finding Only Methods
You might want to focus on only the methods, excluding the fields, properties, and so forth. To do so, you remove the call to GetMembers.
MemberInfo[] mbrInfoArray = theType.GetMembers(BindingFlags.LookupAll);    
Then you add a call to GetMethods.
mbrInfoArray = theType.GetMethods();    
The output now is nothing but the methods.
Output (excerpt)
Boolean Equals(System.Object) is a Method
System.String ToString() is a Method
System.String CreateQualifiedName(System.String, System.String)
is a Method
System.Reflection.MethodInfo get_EntryPoint() is a Method

Finding Particular Members
Finally, to narrow down even further, you can use the FindMembers method to find particular members of the type. For example, you can restrict your search to methods whose names begin with the letters "Get", as you can see in Figure 10.
An excerpt of the output looks like this:
System.Type[] GetTypes() is a Method
System.Type[] GetExportedTypes() is a Method
System.Type GetType(System.String, Boolean) is a Method
System.Type GetType(System.String) is a Method
System.Reflection.AssemblyName GetName(Boolean) is a Method
System.Reflection.AssemblyName GetName() is a Method
Int32 GetHashCode() is a Method
System.Reflection.Assembly GetAssembly(System.Type) is a Method
System.Type GetType(System.String, Boolean, Boolean) is a Method

Dynamic Invocation
Once you've discovered a method, it is possible to invoke it using reflection. For example, you might like to invoke the Cos method of System.Math, which returns the cosine of an angle.
To do so, you will get the Type information for the System.Math class, like so:
Type theMathType = Type.GetType("System.Math");   
With that type information, you can dynamically load an instance of that class.
Object theObj = Activator.CreateInstance(theMathType);    
CreateInstance is a static method of the Activator class, which can be used to instantiate objects.
With an instance of System.Math in hand, you can call the Cos method. To do so, you must prepare an array that will describe the types of the parameters. Since Cos takes a single parameter (the angle whose cosine you want) you need an array with a single member. Into that array you'll put a Type object for the System.Double type, which is the type of parameter expected by Cos.
Type[] paramTypes = new Type[1];
paramTypes[0]= Type.GetType("System.Double");    
You can now pass the name of the method you want and this array describing the types of the parameters to the GetMethod method of the type object retrieved earlier.
MethodInfo CosineInfo = theMathType.GetMethod("Cos",paramTypes);    
You now have an object of type MethodInfo on which you can invoke the method. To do so, you must pass in the actual value of the parameters, again in an array.
Object[] parameters = new Object[1];
parameters[0] = 45;
Object returnVal = CosineInfo.Invoke(theObj,parameters);    
Note that I've created two arrays. The first, paramTypes, held the type of the parameters; the second, parameters, held the actual value. If the method had taken two arguments, you would have declared these arrays to hold two values. If the method took no values, you still would create the array, but you would give it a size of zero!
Type[] paramTypes = new Type[0];    
Odd as this looks, it is correct. Figure 11 shows the complete code.

Conclusion
While there are a number of subtle traps waiting for the unwary C++ programmer, the syntax of C# is not very different from C++ and the transition to the new language is fairly easy. The interesting part of working with C# is working your way through the new common language runtime library, which provides a host of functionality that previously had to be written by hand. This article could only touch on a few highlights. The CLR and the .NET Framework provide extensive support for threading, marshaling, Web application development, Windows-based application development, and so forth.
The distinction between language features and CLR features is a bit blurry at times, but the combination is a very powerful development tool.


Jesse Liberty is the author of a dozen books on software development. He is the president of Liberty Associates Inc. ( http://www.LibertyAssociates.com) where he provides training in .NET technology and contract programming. This article has been adapted from his upcoming book, Programming C#, to be published by O'Reilly & Associates, Inc. in 2001.



========================
Figures 1 ~ 11:
========================

   Figure 1 Using Classes
None.gif using  System;
None.gif
None.gif
//  a class which does not override ToString
None.gif
public   class  Employee
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif   
ExpandedBlockEnd.gif}

None.gif
None.gif
//  A Class which does override ToString
None.gif
public   class  Counter
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif   
private int theVal;
InBlock.gif
InBlock.gif   
public Counter(int theVal)
ExpandedSubBlockStart.gifContractedSubBlock.gif   
dot.gif{
InBlock.gif      
this.theVal = theVal;
ExpandedSubBlockEnd.gif   }

InBlock.gif
InBlock.gif   
public override string ToString()
ExpandedSubBlockStart.gifContractedSubBlock.gif   
dot.gif{
InBlock.gif      Console.WriteLine(
"Calling Counter.ToString()");
InBlock.gif      
return theVal.ToString();
ExpandedSubBlockEnd.gif   }

ExpandedBlockEnd.gif}

None.gif
None.gif
public   class  Tester
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif   
// Note that Main() has a capital M
InBlock.gif   
// and is a static member of the class
InBlock.gif
   public static void Main()
ExpandedSubBlockStart.gifContractedSubBlock.gif   
dot.gif{
InBlock.gif      
// create an instance of the class
InBlock.gif
      Tester t = new Tester();
InBlock.gif
InBlock.gif      
// call the non-static member
InBlock.gif      
// (must be through an instance)
InBlock.gif
      t.Run();
ExpandedSubBlockEnd.gif   }

InBlock.gif
InBlock.gif   
// the non-static method which demonstrates
InBlock.gif   
// calling ToString and boxing
InBlock.gif
   public void Run()
ExpandedSubBlockStart.gifContractedSubBlock.gif   
dot.gif{
InBlock.gif      Employee myEmployee 
= new Employee();
InBlock.gif      Counter myCounter 
= new Counter(12);
InBlock.gif      Console.WriteLine(
"The employee: {0}, the counter value: {1}",
InBlock.gif                        myEmployee, myCounter);
InBlock.gif
InBlock.gif      
// note that integer literals and variables are boxed 
InBlock.gif
      int myInt = 5;
InBlock.gif      Console.WriteLine(
"Here are two integers: {0} and {1}"17, myInt);
ExpandedSubBlockEnd.gif   }

ExpandedBlockEnd.gif}

   Figure 2 Initializing Objects
None.gif using  System;
None.gif
None.gif
//  a simple structure with two 
None.gif
//  member variables and a constructor
None.gif
public   struct  Point
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif   
public Point(int x, int y)
ExpandedSubBlockStart.gifContractedSubBlock.gif   
dot.gif{
InBlock.gif      
this.x = x;
InBlock.gif      
this.y = y;
ExpandedSubBlockEnd.gif   }

InBlock.gif
InBlock.gif   
public int x;
InBlock.gif   
public int y;
ExpandedBlockEnd.gif}

None.gif
None.gif
public   class  Tester
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif   
public static void Main()
ExpandedSubBlockStart.gifContractedSubBlock.gif   
dot.gif{
InBlock.gif      Tester t 
= new Tester();
InBlock.gif      t.Run();
ExpandedSubBlockEnd.gif   }

InBlock.gif
InBlock.gif   
public void Run()
ExpandedSubBlockStart.gifContractedSubBlock.gif   
dot.gif{
InBlock.gif      Point p1 
= new Point(5,12);
InBlock.gif      SomeMethod(p1); 
// fine
InBlock.gif

InBlock.gif      Point p2;  
// create without calling new
InBlock.gif
InBlock.gif      
// this won't compile because the member
InBlock.gif      
// variables of p2 have not been initialized
InBlock.gif      
// SomeMethod(p2); 
InBlock.gif
InBlock.gif      
// initialize them by hand
InBlock.gif
      p2.x = 1;
InBlock.gif      p2.y 
= 2;
InBlock.gif
InBlock.gif      SomeMethod(p2); 
// fine
InBlock.gif

ExpandedSubBlockEnd.gif   }

InBlock.gif
InBlock.gif   
// a method we can pass the Point to
InBlock.gif
   private void SomeMethod(Point p)
ExpandedSubBlockStart.gifContractedSubBlock.gif   
dot.gif{
InBlock.gif      Console.WriteLine(
"Point at {0} x {1}"
InBlock.gif         p.x, p.y);
ExpandedSubBlockEnd.gif   }

ExpandedBlockEnd.gif}


Figure 3 Using Accessors
None.gif private   void  Run()
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    Employee Fred 
= new Employee(25,101,7);
InBlock.gif    Console.WriteLine(
"Fred's age: {0}",
InBlock.gif        Fred.Age);
InBlock.gif    Fred.Age 
= 55;
InBlock.gif    Console.WriteLine(
"Fred's age: {0}",
InBlock.gif        Fred.Age);
InBlock.gif
InBlock.gif    Console.WriteLine(
"Fred's service: {0}",
InBlock.gif        Fred.YearsServed);
InBlock.gif    
// Fred.YearsServed = 12;  // not allowed!
InBlock.gif

ExpandedBlockEnd.gif}

  Figure 4 Index Operator
None.gif public   string   this [ int  index]
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    
get
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        
if (index < 0 || index >= myStrings.Length)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif           
// handle bad index
ExpandedSubBlockEnd.gif
        }

InBlock.gif        
return myStrings[index];
ExpandedSubBlockEnd.gif    }

InBlock.gif    
set
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        myStrings[index] 
= value;
ExpandedSubBlockEnd.gif    }

ExpandedBlockEnd.gif}

  Figure 5 ListBox Class
ContractedBlock.gif ExpandedBlockStart.gif
None.gifusing System;
None.gif  
None.gif    
// a simplified ListBox control
None.gif
public class ListBoxTest
ExpandedBlockStart.gifContractedBlock.gif
dot.gif{
InBlock.gif   
// initialize the list box with strings
InBlock.gif
   public ListBoxTest(params string[] initialStrings)
ExpandedSubBlockStart.gifContractedSubBlock.gif   
dot.gif{
InBlock.gif      
// allocate space for the strings
InBlock.gif
      myStrings = new String[256]; 
InBlock.gif
InBlock.gif      
// copy the strings passed in to the constructor
InBlock.gif
      foreach (string s in initialStrings)
ExpandedSubBlockStart.gifContractedSubBlock.gif      
dot.gif{
InBlock.gif         myStrings[myCtr
++= s;
ExpandedSubBlockEnd.gif      }

ExpandedSubBlockEnd.gif   }

InBlock.gif
InBlock.gif   
// add a single string to the end of the list box
InBlock.gif
   public void Add(string theString)
ExpandedSubBlockStart.gifContractedSubBlock.gif   
dot.gif{
InBlock.gif      myStrings[myCtr
++= theString;
ExpandedSubBlockEnd.gif   }

InBlock.gif
InBlock.gif   
// allow array-like access
InBlock.gif
   public string this[int index]
ExpandedSubBlockStart.gifContractedSubBlock.gif   
dot.gif{
InBlock.gif      
get
ExpandedSubBlockStart.gifContractedSubBlock.gif      
dot.gif{
InBlock.gif         
if (index < 0 || index >= myStrings.Length)
ExpandedSubBlockStart.gifContractedSubBlock.gif         
dot.gif{
InBlock.gif            
// handle bad index
ExpandedSubBlockEnd.gif
         }

InBlock.gif         
return myStrings[index];
ExpandedSubBlockEnd.gif      }

InBlock.gif      
set
ExpandedSubBlockStart.gifContractedSubBlock.gif      
dot.gif{
InBlock.gif         myStrings[index] 
= value;
ExpandedSubBlockEnd.gif      }

ExpandedSubBlockEnd.gif   }

InBlock.gif   
InBlock.gif   
// publish how many strings you hold
InBlock.gif
   public int GetNumEntries()
ExpandedSubBlockStart.gifContractedSubBlock.gif   
dot.gif{
InBlock.gif      
return myCtr;
ExpandedSubBlockEnd.gif   }

InBlock.gif
InBlock.gif   
private string[] myStrings;
InBlock.gif   
private int myCtr = 0;
ExpandedBlockEnd.gif}

None.gif    
None.gif
public class Tester
ExpandedBlockStart.gifContractedBlock.gif
dot.gif{
InBlock.gif   
static void Main()
ExpandedSubBlockStart.gifContractedSubBlock.gif   
dot.gif{
InBlock.gif      
// create a new list box and initialize
InBlock.gif
      ListBoxTest lbt = new ListBoxTest("Hello""World");
InBlock.gif
InBlock.gif      
// add a few strings
InBlock.gif
      lbt.Add("Who");
InBlock.gif      lbt.Add(
"Is");
InBlock.gif      lbt.Add(
"John");
InBlock.gif      lbt.Add(
"Galt");
InBlock.gif
InBlock.gif      
// test the access
InBlock.gif
      string subst = "Universe";
InBlock.gif      lbt[
1= subst;
InBlock.gif
InBlock.gif      
// access all the strings
InBlock.gif
      for (int i = 0;i<lbt.GetNumEntries();i++)
ExpandedSubBlockStart.gifContractedSubBlock.gif      
dot.gif{
InBlock.gif         Console.WriteLine(
"lbt[{0}]: {1}",i,lbt[i]);
ExpandedSubBlockEnd.gif      }

ExpandedSubBlockEnd.gif   }

ExpandedBlockEnd.gif}

   Figure 6 Custom Attributes
ContractedBlock.gif ExpandedBlockStart.gif
None.gifusing System;
None.gif
None.gif
// create custom attribute to be assigned to class members
None.gif
[AttributeUsage(AttributeTargets.Class,
None.gif    AllowMultiple 
= true)]
None.gif
public class BugFixAttribute : System.Attribute
ExpandedBlockStart.gifContractedBlock.gif
dot.gif{
InBlock.gif    
// attribute constructor for 
InBlock.gif    
// positional parameters
InBlock.gif
    public BugFixAttribute
InBlock.gif        (
int bugID, 
InBlock.gif        
string programmer, 
InBlock.gif        
string date)
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        
this.bugID = bugID;
InBlock.gif        
this.programmer = programmer;
InBlock.gif        
this.date = date;
ExpandedSubBlockEnd.gif    }

InBlock.gif
InBlock.gif   
// accessor
InBlock.gif
   public int BugID
ExpandedSubBlockStart.gifContractedSubBlock.gif   
dot.gif{
InBlock.gif        
get
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            
return bugID;
ExpandedSubBlockEnd.gif        }

ExpandedSubBlockEnd.gif    }

InBlock.gif
InBlock.gif    
// property for named parameter
InBlock.gif
    public string Comment
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        
get
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            
return comment;
ExpandedSubBlockEnd.gif        }

InBlock.gif        
set
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            comment 
= value;
ExpandedSubBlockEnd.gif        }

ExpandedSubBlockEnd.gif    }

InBlock.gif
InBlock.gif    
// accessor
InBlock.gif
    public string Date
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        
get
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            
return date;
ExpandedSubBlockEnd.gif        }

ExpandedSubBlockEnd.gif    }

InBlock.gif
InBlock.gif    
// accessor
InBlock.gif
    public string Programmer
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        
get
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            
return programmer;
ExpandedSubBlockEnd.gif        }

ExpandedSubBlockEnd.gif    }

InBlock.gif        
InBlock.gif    
// private member data 
InBlock.gif
    private int     bugID;
InBlock.gif    
private string  comment;
InBlock.gif    
private string  date;
InBlock.gif    
private string  programmer;
ExpandedBlockEnd.gif}

None.gif
None.gif
// ********* assign the attributes to the class ********
None.gif

None.gif[BugFixAttribute(
121,"Jesse Liberty","01/03/05")]
None.gif[BugFixAttribute(
107,"Jesse Liberty","01/04/05"
None.gif    Comment
="Fixed off by one errors")]
None.gif
public class MyMath
ExpandedBlockStart.gifContractedBlock.gif
dot.gif{
InBlock.gif        
InBlock.gif    
public double DoFunc1(double param1)
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        
return param1 + DoFunc2(param1);           
ExpandedSubBlockEnd.gif    }

InBlock.gif
InBlock.gif    
public double DoFunc2(double param1)
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{           
InBlock.gif        
return param1 / 3;
ExpandedSubBlockEnd.gif    }

InBlock.gif    
ExpandedBlockEnd.gif}

None.gif
None.gif
public class Tester
ExpandedBlockStart.gifContractedBlock.gif
dot.gif{
InBlock.gif    
public static void Main()
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        MyMath mm 
= new MyMath();
InBlock.gif        Console.WriteLine(
"Calling DoFunc(7). Result: {0}",
InBlock.gif            mm.DoFunc1(
7));
ExpandedSubBlockEnd.gif    }
        
ExpandedBlockEnd.gif}
Figure 7 Metadata in ILDASM
Figure 7 metadata in ildasm
Figure 8 Printing the Properties
ContractedBlock.gif ExpandedBlockStart.gif
None.gifpublic static void Main()
ExpandedBlockStart.gifContractedBlock.gif
dot.gif{
InBlock.gif   MyMath mm 
= new MyMath();
InBlock.gif   Console.WriteLine(
"Calling DoFunc(7). Result: {0}",
InBlock.gif      mm.DoFunc1(
7));
InBlock.gif
InBlock.gif   
// get the member information and use it to
InBlock.gif   
// retrieve the custom attributes
InBlock.gif
   System.Reflection.MemberInfo inf = typeof(MyMath);
InBlock.gif   
object[] attributes;
InBlock.gif   attributes 
= 
InBlock.gif       Attribute.GetCustomAttributes(inf,
typeof(BugFixAttribute));
InBlock.gif
InBlock.gif   
// iterate through the attributes, retrieving the 
InBlock.gif   
// properties
InBlock.gif
   foreach(Object attribute in attributes)
ExpandedSubBlockStart.gifContractedSubBlock.gif   
dot.gif{
InBlock.gif      BugFixAttribute bfa 
= (BugFixAttribute) attribute;
InBlock.gif      Console.WriteLine(
"\nBugID: {0}", bfa.BugID);
InBlock.gif      Console.WriteLine(
"Programmer: {0}", bfa.Programmer);
InBlock.gif      Console.WriteLine(
"Date: {0}", bfa.Date);
InBlock.gif      Console.WriteLine(
"Comment: {0}", bfa.Comment);
ExpandedSubBlockEnd.gif   }

ExpandedBlockEnd.gif}

  Figure 9 Getting All Members
ContractedBlock.gif ExpandedBlockStart.gif
None.gifpublic class Tester
ExpandedBlockStart.gifContractedBlock.gif
dot.gif{
InBlock.gif    
public static void Main()
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        
// examine a single object
InBlock.gif
        Type theType = Type.GetType("System.Reflection.Assembly");
InBlock.gif        Console.WriteLine(
"\nSingle Type is {0}\n", theType);
InBlock.gif
InBlock.gif        
// get all the members
InBlock.gif
        MemberInfo[] mbrInfoArray = 
InBlock.gif            theType.GetMembers(BindingFlags.LookupAll);
InBlock.gif        
foreach (MemberInfo mbrInfo in mbrInfoArray )
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif                Console.WriteLine(
"{0} is a {1}"
InBlock.gif                    mbrInfo, mbrInfo.MemberType.Format());
ExpandedSubBlockEnd.gif        }

ExpandedSubBlockEnd.gif    }
    
ExpandedBlockEnd.gif}

  Figure 10 Getting Particular Members
ContractedBlock.gif ExpandedBlockStart.gif
None.gifpublic class Tester
ExpandedBlockStart.gifContractedBlock.gif
dot.gif{
InBlock.gif    
public static void Main()
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        
// examine a single object
InBlock.gif
        Type theType = Type.GetType("System.Reflection.Assembly");
InBlock.gif
InBlock.gif        
// just members which are methods beginning with Get
InBlock.gif
        MemberInfo[] mbrInfoArray
InBlock.gif            theType.FindMembers(MemberTypes.Method, 
InBlock.gif                BindingFlags.Default,
InBlock.gif                Type.FilterName, 
"Get*");
InBlock.gif                
foreach (MemberInfo mbrInfo in mbrInfoArray )
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif                 Console.WriteLine(
"{0} is a {1}"
InBlock.gif                     mbrInfo, mbrInfo.MemberType.Format());
ExpandedSubBlockEnd.gif        }

ExpandedSubBlockEnd.gif    }
    
ExpandedBlockEnd.gif}

  Figure 11 Using Reflection
ContractedBlock.gif ExpandedBlockStart.gif
None.gifusing System;
None.gif 
using System.Reflection;
None.gif
None.gif 
public class Tester
ExpandedBlockStart.gifContractedBlock.gif 
dot.gif{
InBlock.gif     
public static void Main()
ExpandedSubBlockStart.gifContractedSubBlock.gif     
dot.gif{
InBlock.gif         Type theMathType 
= Type.GetType("System.Math");
InBlock.gif         Object theObj 
= Activator.CreateInstance(theMathType);
InBlock.gif
InBlock.gif         
// array with one member
InBlock.gif
         Type[] paramTypes = new Type[1];
InBlock.gif         paramTypes[
0]= Type.GetType("System.Double");
InBlock.gif
InBlock.gif         
// Get method info for Cos()
InBlock.gif
         MethodInfo CosineInfo = 
InBlock.gif             theMathType.GetMethod(
"Cos",paramTypes);
InBlock.gif
InBlock.gif         
// fill an array with the actual parameters
InBlock.gif
         Object[] parameters = new Object[1];
InBlock.gif         parameters[
0= 45;
InBlock.gif         Object returnVal 
= CosineInfo.Invoke(theObj,parameters);
InBlock.gif         Console.WriteLine(
InBlock.gif            
"The cosine of a 45 degree angle {0}", returnVal);
InBlock.gif 
ExpandedSubBlockEnd.gif     }

ExpandedBlockEnd.gif }

转载于:https://www.cnblogs.com/Peter-Yung/archive/2007/01/01/609515.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值