zoukankan      html  css  js  c++  java
  • C# Generics

    Source:http://ondotnet.com/pub/a/dotnet/2004/05/17/liberty.html?page=1

    The single most anticipated (and dreaded?) feature of Visual C# 2.0 is the addition of Generics. This article will show you what problems generics solve, how to use them to improve your code, and why you need not fear them.

    What Are Generics?

    Many people find Generics confusing. I believe this is because they are usually explained and demonstrated before the reader understands what problem it is they were designed to solve. You end up with a solution in search of a problem.

    This column will try to stand that process on its head and begin with a simple question: what are generics for? The short answer is this: without generics, it is very difficult to create type-safe collections.

    C# is a type-safe language. Type safety allows potential errors to be trapped by the compiler (reliably), rather than to be found at run time (unreliably, and often only after you've shipped your product!). Thus, in C#, every variable has a defined type; when you assign an object to that variable, the compiler checks to see if the assignment is valid and notifies you if there is a problem.

    In version 1.1 (2003) of .NET, this type safety falls apart when you use collections. All of the collection classes provided by the .NET library are typed to hold objects, and since everything derives from the base class Object, every type can be put into the collection. There is, therefore, effectively no type checking at all.

    Worse, each time you take an object out of a collection you must cast it to the correct type, which incurs a performance hit, and makes for ugly code (and if you mis-cast, throws an exception). Further, if you add a value type (e.g., an integer) to the collection, the integer is implicitly boxed (another performance penalty), and explicitly unboxed when you take it out of the collection (yet another performance penalty and more casting).

    For more on boxing, please see "Trap #4, Watch Out For Implicit Boxing."

    To see these problems in action, we're going to create about as simple a linked list as you can imagine (all of the bells and whistles will be left as an exercise for the reader). For those of you who have never created a linked list, the concept is quite simple. Imagine a chain of boxes; each box (called a Node) contains a bit of data and also a reference to the next box in the chain (except for the last box, which sets its reference to the "next box" to null).

    To create our simplified linked list, we'll need three classes:

    1. The Node class, to contain the data and the reference to the next Node
    2. The LinkedList class, to contain the first Node in the list and any additional information about the list
    3. The test application, to exercise the LinkedList class

    To see how the linked list works, we'll add two types of objects to the list: integers and Employees. You can imagine that an Employee class might contain all of the information needed about each employee in a company. For our purposes, the Employee object can be painfully simple.

    
    
    public class Employee
    {
      
    private string name;
      
    public Employee (string name)
      {
        
    this.name = name;
      }
      
    public override string ToString()
      {
        
    return this.name;
      }
    }

     

    The class consists of nothing but a private string holding the Employee's name, a constructor to set that value, and an override of the ToString() method that returns the value of the Employee's name.

    The linked list itself consists of Nodes. The Node, as noted above, must hold the data (integer or Employee) and a reference to the next Node in the list.

    
    
    代码

    public class Node
    {
      Object data;
      Node next;
      
    public Node(Object data)
      {
        
    this.data = data;
        
    this.next = null;
      }
      
    public Object Data
      { 
        
    get { return this.data; }
        
    set { data = value; }
      }
      
    public Node Next
      {
        
    get { return this.next; }
        
    set { this.next = value; }
      }    

    Note that the constructor sets the private data member to the object that is passed in, and that the next property is initialized to null.

    The class also includes a method, Append, that takes as an argument a new Node, which we'll append to the end of the list. This is accomplished by asking the current Node to see if its next property is null. If so, the current node is the last node in the list; we set that property to point to the new Node and thus have appended the new node to the end of the list.

    If the current Node's next property is not null, then we're not yet at the end of the list. Since the next property is a Node, we call Append on that node, passing in the new Node we were given, thus passing the new Node down the list in search of the end. Eventually it will find the end, and be appended there.

    
    
    public void Append(Node newNode)
    {
      
    if ( this.next == null )
      {
        
    this.next = newNode;
      }
      
    else
      {
        next.Append(newNode);
      }
    }

    The ToString method is overridden in Node, both to return the value of the data, and also to tell the next Node in the list to return the value of its data.

    
    
    public override string ToString()
    {
      
    string output = data.ToString();
      
    if ( next != null )
      {
        output 
    += "" + next.ToString();
      }
      
    return output;
    }

    Thus, when you invoke ToString() on the head Node, you see the value of all of the data in the list.

    The LinkedList class itself holds a reference to a single Node, the headNode, initialized to null.

    
    

    public class LinkedList
    {
      Node headNode 
    = null;

    The LinkedList class needs no constructor (relying on the default constructor provided by the compiler), but we will provide a public method, Add, that will take data to store in the linked list. This method will check to see if the headNode is null; if so, it will create the headNode with that data. If not, it will create a new Node and pass that Node, with the data, to the headNode's Append method, which works as described above.

    
    
    public void Add(Object data)
    {
      
    if ( headNode == null )
      {
        headNode 
    = new Node(data);
      }
      
    else
      {
        headNode.Append(
    new Node(data));
      }
    }

    To provide a bit of collection semantics, we'll create an indexer for the linked list:

    
    
    代码
    public object thisint index ]
    {
      
    get
      {
        
    int ctr = 0;
        Node node 
    = headNode;
        
    while ( node != null  && ctr <= index )
        {
          
    if ( ctr == index )
          {
            
    return node.Data;
          }
          
    else
          {
            node 
    = node.Next;
          }
          
    ++ctr;
        } 
    // end while
        return null;
      }    
    // end get
    }      // end indexer


     
    Finally, ToString is overridden to invoke the headNode's ToString method.
    
    
    public override string ToString()
    {
      
    if ( this.headNode != null )
      {
        
    return this.headNode.ToString();
      }
      
    else
      {
        
    return string.Empty;
      }
    }

    We can test our linked list by adding integers to the list:

    
    
    public void Run()
    {
      LinkedList ll 
    = new LinkedList();
      
    for ( int i = 0; i < 10; i ++ )
      {
        ll.Add(i);
      }
      Console.WriteLine(ll);
      Console.WriteLine(
    "  Done. Adding employees...");

    If you test just this portion of the code, it works as expected.

    
    0, 1, 2, 3, 4, 5, 6, 7, 8, 9
    Done. Adding employees...
    

    However, since this is a collection of objects, you are equally free to add Employee objects to the collection:

    
    
    ll.Add(new Employee("John"));
    ll.Add(
    new Employee("Paul"));
    ll.Add(
    new Employee("George"));
    ll.Add(
    new Employee("Ringo"));

    Console.WriteLine(ll);
    Console.WriteLine(
    "  Done.");

    The output shows that both the integers and the Employees are stored in the same collection:

    
    0, 1, 2, 3, 4, 5, 6, 7, 8, 9
    Done. Adding employees...
    0, 1, 2, 3, 4, 5, 6, 7, 8, 9, John, Paul, George, Ringo
    Done.
    

    While this seems quite convenient, the downside is that you've lost all type safety. Furthermore, since the linked list expects objects, each integer that is added is silently boxed, as shown in the IL code:

    
    IL_000c:  box        [mscorlib]System.Int32
    IL_0011:  callvirt   instance void ObjectLinkedList.LinkedList::Add(object)
    

    Also, as noted above, when you want to retrieve the items from your list, the integers must be explicitly unboxed (cast to integers) and the Employees must be cast to Employee objects.

    
    
    Console.WriteLine("The fourth integer is " + Convert.ToInt32(ll[3]));
    Employee d 
    = (Employee) ll[11];
    Console.WriteLine(
    "The second Employee is " + d);

    The solution to all of these problems is to create type-specific collections. An Employee LinkedList would not take Objects; it would take Employee instances (or instances of types derived from Employee). This would add type safety, and remove the need for casting. An integer LinkedList would have the advantage of not requiring boxing and unboxing (since the collection would not expect Objects).

    As an example, you could create an EmployeeNode that would know that its data is of type Employee:

    
    
    public class EmployeeNode 
    {
      Employee employeedata;
      EmployeeNode employeeNext;

    Append now takes an EmployeeNode. You also need to create a new EmployeeLinkedList that takes the new EmployeeNode:

    
    
    public class EmployeeLinkedList
    {
      EmployeeNode headNode 
    = null;

    The EmployeeLinkedList.Add method does not take an object, but rather takes an Employee:

    
    

    public void Add(Employee data)
    {
      
    if ( headNode == null )
      {
        headNode 
    = new EmployeeNode(data);
      }
      
    else
      {
        headNode.Append(
    new EmployeeNode(data));
      }
    }

    Similarly, the indexer must be modified to take EmployeeNodes, etc. This does solve the problems of casting and boxing, and adds type safety. You can now add Employees (but not integers!) to this new linked list, and when you take Employees out, no cast is required:

    
    
    代码
    EmployeeLinkedList employees = new EmployeeLinkedList();
    employees.Add(
    new Employee("Stephen King"));
    employees.Add(
    new Employee("James Joyce"));
    employees.Add(
    new Employee("William Faulkner"));
    /* employees.Add(5);  // try to add an integer - won't compile */
    Console.WriteLine(employees);
    Employee e 
    = employees[1];
    Console.WriteLine(
    "The second Employee is " + e);

    What is particularly nice is that unless there is an implicit conversion from integer to Employee, the code that tries to add an integer will not even compile!

    What is not so nice is that each time you want to create a type-specific list, you have a lot of cutting and pasting to do. Not good, not reusable. Also, if you are the creator of the class, you can not anticipate in advance what types might be stored in the linked list, and so you force your user to do the work of adding type safety. Yuck.

    Generics To The Rescue

    The solution, as you have probably guessed by now, is to use Generics. With Generics, you retain the generic quality of the linked list (one implementation for all types), but you add type safety by defining the type of objects you'll add to the list when you instantiate it. The implemenation is frighteningly simple. Return to your original Node class:

    
    
    public class Node
    {
      Object data;

    Notice the type of the data it holds is Object (in the EmployeeNode, it was Employee). We'll change this to a generic type (typically indicated by a single letter, in this case T). We'll also define the Node class to indicate that it is being used generically to take a type T:

    
    

    public class Node <T>
    {
        T data;

    You read this as "class Node of T." The T stands for the type to be declared when the Node is instantiated. T might turn out to be type Object, or it might be integer or Employee. That will be decided when the Node is instantiated.

    Note: the use of T is arbitrary; it is a mnemonic to remind you that it stands for a generic type. You could just as easily have defined this

    
    
    public class Node <UnknownType>
    {
        UnknownType data;

    Having used T for the unknown type, the next member (the reference to the next Node) must refer to a Node of T (that is, a generic Node that takes a type T):

    
    Node<T> next;
    

    The constructor takes a single argument, of type T:

    
    
    public Node(T data)
    {
        
    this.data = data;
        
    this.next = null;
    }

    The rest of the Node class is similar; everywhere you had Object, you now put T (see the complete code listing). The LinkedList now takes a Node of T rather than a simple Node as its headNode:

    
    
     public class LinkedList<T>
     {
         Node
    <T> headNode = null;

    Once again, the transformation is pretty straightforward. Anywhere you would have had Object, you have T, and anywhere you would have had Node, you now have Node<T>. The test code instantiates two linked lists. One is a linked list of integers:

    
    
    LinkedList<int> ll = new LinkedList<int>();

    And the second is a linked list of Employee objects:

    
    
    LinkedList<Employee> employees = new LinkedList<Employee>();

    The rest of the code is unchanged from the first version, except that there no longer is any boxing of the integer, there is no need to cast the Employees, and it is not possible to put the wrong type into the collection!

    
    
    LinkedList<int> ll = new LinkedList<int>();
    for ( int i = 0; i < 10; i ++ )
    {
        ll.Add(i);
    }

    Generics thus allow you to create type-safe collections without having to duplicate code. Even better, because the generic types are expanded to their specific types at run time, the Just In Time compiler is able to share code among different instances, dramatically reducing the code bloat that you see, for example, in C++.

    As a final tip to using Generics, my general approach is to write my generic class first using a specific type (e.g., Employee) and get it working. Once it is fully debugged, I then genericize it, converting the type from the specific Object or Employee to the generic T.

  • 相关阅读:
    【模板】O(nlongn)求LIS
    【图论】用线段树写Dijikstra!!
    【图论】最短路总结
    【codeforces841A】Generous Kefa
    【模板】Tarjian求LCA
    【模板】 RMQ求区间最值
    【模板】map入门
    【二分 贪心】覆盖问题 BZOJ1052 HAOI2007
    【dp】10-15题解 snake vs block
    【模拟】10-15 题解 trans
  • 原文地址:https://www.cnblogs.com/xgw2004058/p/1756396.html
Copyright © 2011-2022 走看看