新书推介:《语义网技术体系》
作者:瞿裕忠,胡伟,程龚
   XML论坛     W3CHINA.ORG讨论区     计算机科学论坛     SOAChina论坛     Blog     开放翻译计划     新浪微博  
 
  • 首页
  • 登录
  • 注册
  • 软件下载
  • 资料下载
  • 核心成员
  • 帮助
  •   Add to Google

    >> 本版讨论.NET,C#,ASP,VB技术
    [返回] 中文XML论坛 - 专业的XML技术讨论区计算机技术与应用『 Dot NET,C#,ASP,VB 』 → C++ -> C#(转载) 查看新帖用户列表

      发表一个新主题  发表一个新投票  回复主题  (订阅本版) 您是本帖的第 2903 个阅读者  浏览上一篇主题  刷新本主题   树形显示贴子 浏览下一篇主题
     * 贴子主题: C++ -> C#(转载) 举报  打印  推荐  IE收藏夹 
       本主题类别:     
     admin 帅哥哟,离线,有人找我吗?
      
      
      
      威望:9
      头衔:W3China站长
      等级:计算机硕士学位(管理员)
      文章:5255
      积分:18407
      门派:W3CHINA.ORG
      注册:2003/10/5

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给admin发送一个短消息 把admin加入好友 查看admin的个人资料 搜索admin在『 Dot NET,C#,ASP,VB 』的所有贴子 点击这里发送电邮给admin  访问admin的主页 引用回复这个贴子 回复这个贴子 查看admin的博客楼主
    发贴心情 C++ -> C#(转载)


    发信人: Erratic (漂砾), 信区: DotNET        
    标  题: C++ -> C#(转载)
    发信站: BBS 水木清华站 (Thu Jun 28 21:03:30 2001)


    From MSDN Magazine

    C++ -> C#: What You Need to Know to Move from C++ to C#  
    Jesse Liberty  
      
    This article assumes you're familiar with C++  
    Level of Difficulty     1   2   3    
    Download the code for this article: CtoCsharp(41KB)   
    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.  
    ------------------------------------------------------------------------------
    --
      
    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, http://msdn.microsoft.com/msdnmag/issues/0900/csha
    rp/csharp.asp) 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 (see http://www.LibertyAssociates.com/Books&Resources.htmand click  
    on the FAQ for Programming  
    C#), 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.

    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.

    public int Age
    {
        get
        {
            return age;
        }
        set
        {
            age = value;
        }
    }

    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.

    public int YearsServed
    {
        get
        {
            return yearsServed;
        }
    }

    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:

    int[,] myRectangularArray =   
    {
        {0,1,2}, {3,4,5}, {6,7,8}, {9,10,11}
    };

    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:

    myJaggedArray[0] = new int[5];  
    myJaggedArray[1] = new int[2];  
    myJaggedArray[2] = new int[3];  
    myJaggedArray[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.

    string theFirstString = myListBox[0];
    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.

    public IEnumerator GetEnumerator()
    {
        return (IEnumerator) new ListBoxEnumerator(this);
    }

    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.

    public ListBoxEnumerator(ListBoxTest theLB)
    {
        myLBT = theLB;
        index = -1;
    }

    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.

    public bool MoveNext()
    {
        index++;
        if (index >= myLBT.myStrings.Length)
            return false;
        else
            return true;
    }

    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.

    public object Current
    {
        get
        {
            return(myLBT[index]);
        }
    }

    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:

    using System;
    using System.IO;
    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.

    public class AsynchIOTester
    {
        private Stream inputStream;        
        private byte[] buffer;           
        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:

    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

    void OnCompletedRead(IAsyncResult asyncResult)

    and then to hook up the delegate in the constructor:

    AsynchIOTester()
    {
          ???
         myCallBack = new AsyncCallback(this.OnCompletedRead);
    }

    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:

    public static void Main()
    {
        AsynchIOTester theApp = new AsynchIOTester();     
        theApp.Run();
    }

    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.

    AsynchIOTester()
    {
        inputStream = File.OpenRead(@"C:\MSDN\fromCppToCS.txt");
        buffer = new byte[BUFFER_SIZE];
        myCallBack = new AsyncCallback(this.OnCompletedRead);
    }

    In the Run method, you call BeginRead, which will cause an asynchronous read  
    of the file.

    inputStream.BeginRead(
         buffer,             // where to put the results
         0,                  // offset
         buffer.Length,      // how many bytes (BUFFER_SIZE)
         myCallBack,         // call back delegate
         null);              // local state object

    You then go on to do other work.

    for (long i = 0; i < 50000; i++)         
    {
        if (i%1000 == 0)
        {
            Console.WriteLine("i: {0}", i);
        }
    }

    When the read completes, the CLR will call the callback method.

    void OnCompletedRead(IAsyncResult asyncResult)
    {

    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.

    if (bytesRead > 0)
    {
        String s = Encoding.ASCII.GetString(buffer, 0, bytesRead);
        Console.WriteLine(s);
        inputStream.BeginRead(buffer, 0, buffer.Length,
                              myCallBack, null);
    }

    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.

    if (socketForClient.Connected)
    {
    ???

    Next you have to create a NetworkStream class, passing the socket in to the  
    constructor.

    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.

    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.

    TCPClient socketForServer;
    socketForServer = new TCPClient("localHost", 65000);

    With this TCPClient, you can create a NetworkStream, and on that stream  
    create a StreamReader.

    NetworkStream networkStream = socketForServer.GetStream();
    System.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.

    do
    {
        outputString = streamReader.ReadLine();

        if( outputString != null )
        {
            Console.WriteLine(outputString);
        }
    }
    while( outputString != null );

    To test this, you create a simple test file:

    This is line one
    This is line two
    This is line three
    This is line four

    Here is the output from the server:

    Output (Server)
    Client connected
    Sending This is line one
    Sending This is line two
    Sending This is line three
    Sending This is line four
    Disconnecting from client...
    Exiting...

    And here is the output from the client:

    This is line one
    This is line two
    This is line three
    This 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.
      

    ------------------------------------------------------------------------------
    --
    For related articles see:
    C# Offers the Power of C++ and Simplicity of Visual Basic  
    For background information see:
    Getting Started with the .NET Framework  
      

    ------------------------------------------------------------------------------
    --
    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.

    ------------------------------------------------------------------------------
    --
      
    From the July 2001 issue of MSDN Magazine.
      
      
    --

       一剑走天下
       两樽论春秋


    ※ 来源:·BBS 水木清华站 smth.org·[FROM: 166.111.214.7]
    上一篇
    返回上一页
    回到目录
    回到页首
    下一篇


       收藏   分享  
    顶(0)
      




    ----------------------------------------------

    -----------------------------------------------

    第十二章第一节《用ROR创建面向资源的服务》
    第十二章第二节《用Restlet创建面向资源的服务》
    第三章《REST式服务有什么不同》
    InfoQ SOA首席编辑胡键评《RESTful Web Services中文版》
    [InfoQ文章]解答有关REST的十点疑惑

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2004/11/9 2:25:00
     
     GoogleAdSense
      
      
      等级:大一新生
      文章:1
      积分:50
      门派:无门无派
      院校:未填写
      注册:2007-01-01
    给Google AdSense发送一个短消息 把Google AdSense加入好友 查看Google AdSense的个人资料 搜索Google AdSense在『 Dot NET,C#,ASP,VB 』的所有贴子 点击这里发送电邮给Google AdSense  访问Google AdSense的主页 引用回复这个贴子 回复这个贴子 查看Google AdSense的博客广告
    2025/6/22 19:21:28

    本主题贴数1,分页: [1]

    管理选项修改tag | 锁定 | 解锁 | 提升 | 删除 | 移动 | 固顶 | 总固顶 | 奖励 | 惩罚 | 发布公告
    W3C Contributing Supporter! W 3 C h i n a ( since 2003 ) 旗 下 站 点
    苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》
    148.438ms