Chapter 6 - Collections and Generics

The content and code of this article is referenced from book Pro C#5.0 and the .NET 4.5 Framework by Apress. The intention of the writing is to review the konwledge and gain better understanding of the .net framework. 

 

When the .net platform was released, programmers frequently used the classes of the System.Collections namespace to store and interact with bits of data used within an application. In .net 2.0, the C# programming language was enhanced to support a feature termed generics; and with this change, a brand new namespace was introduced in the base class libraries: System.Collections.Generic. 

 

1. The motivation for Collection class

The most primitive container one could use to hold application data is undoubtly the array. C# array allow you to define a set of identically typed items of a fixed upper limit. 

 1         public static void Main (string[] args)
 2         {
 3             string[] myStrings = { "string1", "string2", "string3" };
 4 
 5             foreach (string s in myStrings) {
 6                 Console.WriteLine (s);
 7             }
 8 
 9             Array.Sort (myStrings);  //sort array
10             Array.Reverse (myStrings); //reverse array
11         }

While basic arrays can be useful to manage small amount of fixed-size data, there are many other times where you require a more flexible data structure, such as dynamically growing and shinking container. .Net base class library ships with a number of namespace containing collection classes. And they are built to dynamically resize themselves on the fly as you insert or remove items. 

You'll notice that a collection class can belong to one of two broad categories: nongeneric collections, generic collections. On the one side, nongeneric collections are typically designed to operate on System.Object type and are , therefore, very loosely typed containers. In contrast, generic collections are much more type safe, given that you must specify "type of type" they contain upon creation . 

 

2. System.Collections Namespace

Any .net application built with .net 2.0 or higher should ignore the classes in System.Collections in favor of corresponding classes in System.Collections.Generic. However, it is still important to know the basics of the nongeneric collection class. 

ClassMeaningKey Implement interfaces
ArrayListRepresents a dynamically sized collection of objects listed in sequenial orderIList, ICollection, IEnumerable ICloneable
BitArrayManages a compact array of bit values, which are represented as BooleanICollection, IEnumerable ana ICloneable
HashtableRepresent a collection of key/value pairs that are organized based on the hash code of the keyIDictionary ICollection, IEnumerable and ICloneable
QueueRepresents a standard first-in, first-out collection of objects. ICollection, IEnumerable, and ICloneable
SortedListRepresents a collection of key/value pairs that are sorted by the keys and are accessible by key and by indexIDictionary, ICollection, IEnumerable, ICloneable
StackA last-in, first-out stack providing push and pop functionalityICollection, IEnumerable, and ICloneable

 

Key interfaces supported by classes of System.Collections

Interface      Meaning
ICollectionDefines general characteristics for all nongeneric collection types
ICloneableAllows the implementing object to return a copy of itself to the caller
IDictionaryAllows a nongeneric collection object to represent its contents using key/value paris
IEnumerablereturns an object implementing the IEnumerator interface
IEnumerator  enables foreach style iteration of collection items
IListProvides behavior to add, remove, and index items in a sequential list of objects

 

2.1 Working with ArrayList

ArrayList, you can add or remove items on the fly and container automatically resize itself accordingly:

 1         public static void Main (string[] args)
 2         {
 3             ArrayList strArray = new ArrayList ();
 4             strArray.AddRange (new string[] {"First", "Second", "Third" });
 5 
 6             Console.WriteLine ("This collection has {0} items", strArray.Count);
 7 
 8             //add a new item and display current count
 9             strArray.Add("Fourth!");
10             Console.WriteLine ("This collection has {0} items", strArray.Count);
11 
12             //display all items
13             foreach (string s in strArray) {
14                 Console.WriteLine (s);
15             }
16 
17         }

 

2.2 System.Collections.Specialized Namespace

System.Collection.Specialized namespace defines a number of specialied collection types. 

TypeMeaning
HybridDictionaryThis class implements IDictionary by using a ListDictionary while the collection is small, and then swithing to a Hashtable when the collection gets large
ListDictionaryThis class is useful when you need to manage a small number of items that can change overtime. 
StringCollectionThis class provides an optimal way to manage large collections of string data
BitVector32This class provides a simple structure that stores boolean values and small integers in 32 bits of memory. 

 

While these specialized types might be just what your projects require in some situation, I won't comment on their usage here. In many cases, you will likely find the System.Collections.Generic namespace provides classes with similar functionality and additional benefits. 

 

2.3 The problem of non-generic collection

The first issue is that using the System.Collections and System.Collection.Specialied classes can result in some poorly performing code, especially when you are manipulating numerical data. The CLR must perform a number of memory transfer operations when you store structures in any nongeneric collection class prototyped to operate on System.Object. 

Second issue is that most of the nongeneric collection classes are not type safe, they were deveoloped to operate on System.Objects, and they could therefore contain anything at all. 

 

2.4 The issue of performance

C# provides a simple mechanism, termed boxing, to store the data in a value type within a reference variable. 

1         static void SimpleBoxOperation()
2         {
3             int myInt = 23;
4             object boxInt = myInt; //boxing operation
5         }

Boxing can be formally defined as the process of explicitly assigning a value type to a System.Object variable. When you box a value, the CLR allocates a new object on the heap and copies and the value type's value into that instance. What is returned to you is a reference to the newly allocated heap-based object.

The opposite operation is unboxing, the process of converting the value held in the project reference back into a corrsponding value type on the stack. The CLR first values if the receiving data type is equivalent to the boxed type, if so, it copies the vlaue back into a local stack-based variable. 

1         static void SimpleBoxOperation()
2         {
3             int myInt = 23;
4             object boxInt = myInt; //boxing operation
5 
6             int unboxInt = (int)boxInt; //unboxing 
7         }

The InvalidCastException will be thrown if you're attempting to unbox the int into a long type. 

Boxing and unboxing are convenient from a programmer's viewpoint, but this simplified approach to stack/heap memory transfer comes with the baggages of performance issues. 

 

3. Generic Collection

When you use Generic collection, you rectify all of the previous issues, including boxing/unboxing and a lack of type safety. 

 1         public static void Main (string[] args)
 2         {
 3             List<int> ints = new List<int> ();
 4             ints.Add (10);
 5             ints.Add (9);
 6             ints.Add (8);
 7 
 8             foreach (int i in ints) {
 9                 Console.WriteLine (i);
10             }
11         }

Generics provide better performance because they do not result in boxing or unboxing penalities when storing value types. Generics are type safe because they can contain only the type of type you specify. 

 

3.1 Specifying type parameters for generic classes/structures

When you create an instance of a generic class or structure, you specify the type parameter when you delcare the variable and when you invoke the constructor. 

List<Person> morePeople = new List<Person>();

You can read it as a list of person objects. After you specify the type parameter of a generic item, it cannot be changed. 

In previous chpater, we learned about a number of nongenerice interfaces, such as IComparable, IEnumerable and IEnumerator. And we have implmented those interface on class. 

 1 public class Car : IComparable
 2     {
 3         public int CarID { get; set;} //auto property
 4 
 5         public Car ()
 6         {
 7         }
 8 
 9         public int CompareTo(object c)
10         {
11             Car newCar = c as Car; //cast object to car
12             if (newCar != null) {
13                 if (this.CarID > newCar.CarID)
14                     return 1;
15                 if (this.CarID < newCar.CarID)
16                     return -1;
17                 else
18                     return 0;
19             } 
20             else 
21             {
22                 Console.WriteLine ("invalid car type");
23             }
24         }
25     }

And because the interface is nongeneric, the object type is used to as parameter and we have to cast it to appropriate type before processing. Now assume you use the generic counterpart of this interface. 

    public class Car : IComparable<Car>
    {
        public int CarID { get; set;} //auto property

        public Car ()
        {
        }
        public int CompareTo(Car newCar)
        {
            if (this.CarID > newCar.CarID)
                return 1;
            if (this.CarID < newCar.CarID)
                return -1;
            else
                return 0;
        }
    }

Here, you do not need to check incoming parameter is a Car because it can only be a Car. If Someone were to pass in an incompatible data type, you would get a compile-time error. 

 

3.2 The System.Collections.Generic namespace

When you are building a .net application and need a way to manage in-memory data, the classes of system.collections.Generic will most likely fit the bill. 

InterfaceMeaning
ICollection<T>defines a general characteristics
IComparer<T>Defines a way to compare to objects
IDictionary<T>Allows a generic collection object to represent its contents using key/value pairs
IEnumerable<T>Returns the IEnumerator<T> interface for a given object
IEnumerator<T>Enables foreach-style iteration over a generic collection
IList<T>Provides behavior to add, remove, and index items in a sequential list of objects
ISet<T>  provides the base interface for the abstract of sets

The System.Collections.Generic namespace also defines serveral classes that implement many of these key interfaces. 

Generic ClassKey interfaceMeaning
Dictionary<TKey, TValue>ICollection<T>, IDictionary<TKey, TValue>, IEnumerable<T>This represent a generic collection of keys and vlaues
LinkedList<T>ICollection<T>, IEnumerable<T>This represents a doubly linked list
List<T>ICollection<T>, IEnmerable<T>, IList<T>  This is a dynamically resizable sequential list of items
Queue<T>ICollection, IEnumerable<T>This is a generic implementation of first-in, first-out
SortedDictionary<TKey, TValue>ICollection<T>, IDictionary<TKey, TValue>This is a generic implementation of a sorted set of key/value pairs
SortedSet<T>ICollection<T>, IEnumerable<T>, ISet<T>This represents a collection of objects that is maintained in sorted order with no duplication
Stack<T>  ICollection, IEnumerable<T>This is generic implementation of a last-in, first-out list

 

3.3 Collection initialization syntax

This language feature makes it possible to populate many container with items by using syntax similar to what you use to populate a basic array. 

        int[] ints = { 0, 1, 2, 3, 4 };
            List<int> listInts = new List<int> { 0, 2, 3, 5 };
            ArrayList mList = new ArrayList { 3, 4, 9 };

 

3.4 Working with the List<T> class

The List<T> class is bound to be your most frequently used type in the System.Collection.Generic because it allows you to resize the contents of the container dynamically. 

 1         public static void UseGenericList()
 2         {
 3             List<Person> people = new List<Person> ()
 4             {
 5                 new Person{FirstName = "person1", LastName="test", Age= 20},
 6                 new Person{FirstName = "person2", LastName="test", Age= 26},
 7                 new Person{FirstName = "person3", LastName="test", Age= 23},
 8             };
 9 
10             Console.WriteLine ("There are {0} People", people.Count);
11 
12             foreach (Person p in people) {
13                 Console.WriteLine (p.FirstName);
14             }
15 
16             //insert new person
17             people.Insert(2, new Person{FirstName="Person4", LastName="test", Age=40});
18             Console.WriteLine ("There are {0} people", people.Count);
19 
20             //copy data to array
21             Person[] arrayOfPeople = people.ToArray();
22             for (int i = 0; i < arrayOfPeople.Length; i++) {
23                 Console.WriteLine (arrayOfPeople [i].FirstName);
24             }
25 
26         }

Here, you use initialization syntax to pupulate List<T> with objects, as a shorthand notation for calling Add() multiple times. 

 

3.5 Working with Stack<T> class

The stack class represents a collection that maintains items using a last-in, first-out manner. As you might expect, Stack<T> defines members named Push() and Pop() to place items onto or remove items from the stack. 

 1         public static void UseGenericStack()
 2         {
 3             Stack<Person> stackOfPeople = new Stack<Person> ();
 4 
 5             stackOfPeople.Push (new Person{ FirstName = "person1", LastName = "test", Age = 10 });
 6             stackOfPeople.Push (new Person{ FirstName = "person2", LastName = "test", Age = 11 });
 7             stackOfPeople.Push (new Person{ FirstName = "person3", LastName = "test", Age = 13 });
 8 
 9             //look at the top item, pop it and look again
10             Console.WriteLine("First person is {0}", stackOfPeople.Peek().FirstName);
11             Console.WriteLine ("Popped off {0}", stackOfPeople.Pop ());
12 
13             Console.WriteLine("First person is {0}", stackOfPeople.Peek().FirstName);
14             Console.WriteLine ("Popped off {0}", stackOfPeople.Pop ());
15 
16             Console.WriteLine("First person is {0}", stackOfPeople.Peek().FirstName);
17             Console.WriteLine ("Popped off {0}", stackOfPeople.Pop ());
18 
19             try{
20                 Console.WriteLine("First person is {0}", stackOfPeople.Peek().FirstName);
21                 Console.WriteLine ("Popped off {0}", stackOfPeople.Pop ());
22             }
23             catch(InvalidOperationException ex) {
24                 Console.WriteLine (ex.Message);
25             }
26         }

 

3.6 Working with Queue<T> class

Queues are containers that ensure items are accessed in first-in, first-out manner. 

member of Queue<T>Meaning
Dequeue()Removes and returns the object at the beginning of the Queue<T>
Enqueue()Adds an object to the end of the Queue<T>
Peek()Returns the object at the beginning of the Queue<T> without removing it
 1         public static void UseGenericQueue()
 2         {
 3             Queue<Person> peopleQ = new Queue<Person> ();
 4             peopleQ.Enqueue (new Person{ FirstName = "test1", LastName = "test", Age = 13 });
 5             peopleQ.Enqueue (new Person{ FirstName = "test2", LastName = "test", Age = 13 });
 6             peopleQ.Enqueue (new Person{ FirstName = "test3", LastName = "test", Age = 13 });
 7 
 8             //peek at the first person
 9             Console.WriteLine("{0} is in the first position", peopleQ.Peek().FirstName);
10             //remove the first person
11             peopleQ.Dequeue ();
12             Console.WriteLine("{0} is in the first position", peopleQ.Peek().FirstName);
13         }

 

3.7 Working with SortedSet<T> class

The SortedSet<T> class is useful because it automatically ensures that the items in the set are sorted when you insert or remove items. However, you do need to inform it how you want to sort the objects, by passing in as a constructor argument an object that implements the generic IComparer<T> interface. 

 

 1         static void SortedSet()
 2         {
 3             SortedSet<Person> setOfPeople = new SortedSet<Person> (new SortByAge ()) {
 4                 new Person{ FirstName = "test1", LastName = "test", Age = 20 },
 5                 new Person{ FirstName = "test2", LastName = "test", Age = 34 },
 6                 new Person{ FirstName = "test3", LastName = "test", Age = 40 }
 7             };
 8 
 9             foreach (Person p in setOfPeople) {
10                 Console.WriteLine (p.Age);
11             }
12 
13             //add other people
14             setOfPeople.Add(new Person{FirstName="test4", LastName="test", Age=5});
15             setOfPeople.Add(new Person{FirstName="test4", LastName="test", Age=80});
16             foreach (Person p in setOfPeople) {
17                 Console.WriteLine (p.Age);
18             }
19 
20         }

As the result, the list is sorted by person's age. 

 

4. System.Collections.ObjectModel

Now, we can briefly examine an additional collection-centric namspace, System.Collections.ObjectModel. This is a relatively small namespace, which contains a handful of classes. 

System.Collections.ObjectModel TypeMeaning
ObservableCollection<T>Represents a dynamic data collection that provides notifications when items get added, removed, or when the whole list is refreshed
ReadOnlyObservableCollection<T>Represents a read-only version of ObservableCollection<T>

 

The observableCollection<T> class is very userful in that it has the ability to inform external objects when its contents have changed in some way. 

 

4.1 Working with ObservableCollection<T>

In many ways, working with the ObservableCollection<T> is identical to working with List<T>, given that both of these classes implement the same core interfaces. What makes it unique is that this class supports an event named CollectionChanged. This event will fire whenever a new item is inserted, a current item is removed. 

 1         public static void Main (string[] args)
 2         {
 3             ObservableCollection<Person> people = new ObservableCollection<Person> () {
 4                 new Person{FirstName="test1", LastName ="test", Age=20},
 5                 new Person{FirstName="test2", LastName ="test", Age=23},
 6             };
 7                 
 8             people.CollectionChanged += (object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) => {
 9                 Console.WriteLine("Action for this event {0}", e.Action.ToString());
10                 switch(e.Action)
11                 {
12                 case NotifyCollectionChangedAction.Remove:
13                     Console.WriteLine("Old items");
14                     foreach(Person p in e.OldItems)
15                     {
16                         Console.WriteLine(p.FirstName);
17                     }
18                     break;
19                 case NotifyCollectionChangedAction.Add:
20                     Console.WriteLine("New items");
21                     foreach(Person p in e.NewItems)
22                     {
23                         Console.WriteLine(p.FirstName);
24                     }
25                     break;
26                 default:
27                     break;
28                 }
29             };
30 
31             //add one person
32             people.Add (new Person{ FirstName = "test3", LastName = "test", Age = 32 });
33 
34         }

 

5. Create custom generic methods

While most developers typically use the existing generic types within the base class libraries, it is also possible to build your own generic members and custom generic types. When you build custom generic methods, you achieve a supercharnged version of traditional method overloading. While overloading is a useful feature in an object-oriented language, one problem is that you can easily end up with a ton of methods that essentially do the same thing.  

 1         static void Swap(ref int a, ref int b)
 2         {
 3             int temp;
 4             temp = a;
 5             a = b;
 6             b = temp;
 7         }
 8 
 9         static void Swap(ref Person a , ref Person b)
10         {
11             Person temp;
12             temp = a;
13             a = b;
14             b = temp;
15         }
16 
17         //one generic methods
18         static void Swap<T>(ref T a, ref T b)
19         {
20             T temp;
21             temp = a;
22             a = b;
23             b = temp;
24         }

As you can see, we can write one generic mehtod, instead of creating two methods basically doing the same thing. 

 

 6. Create Custom Generic Structures and Classes

It's time to turn your attention to the construction of a gennric structure or class. 

 1     public struct Point<T>
 2     {
 3         private T xPos;
 4         private T yPos;
 5 
 6         public Point(T xValue, T yValue)
 7         {
 8             xPos = xValue;
 9             yPos = yValue;
10         }
11 
12         public T X
13         {
14             get{ return xPos; }
15             set{ xPos = value; }
16         }
17 
18         public T Y
19         {
20             get { return yPos; }
21             set { yPos = value; }
22         }
23     }

 

6.1 The default keyword in generic code

With the introduction of generic, the C# default keyword has been given a dual identity. In addition to its use within a swith construct, it can also be used to set a type parameter to its default value. This is helpful because generic type does not know the actual placeholders up front.  Numeric values have a default value of 0, reference types have a default value of null. 

    public void ResetValue()
        {
            xPos = default(T);
            yPos = default(T);
        }

 

6.2 Constraining type parameters

In this chapter, any generic item has at least one type parameter that you need to specify at the time you interact with the generic type or member. This alone allows you to build some type-safe code; however, the .net platform allows you to use the where keyword to get extremely specific about what a given type parameter must look like. 

Using this keyword, you can add a set of constraints to a given type parameter, which the C# compiler will check at the compile time. 

GenericMeaning
Where T : structThe type parameter <T> must have System.ValueType in its chain of inheritance (i.e <T> must be structure).
Where T : classThe type parameter <T> must not have System.ValueType in it chain of inheritance (i.e, <T> must be a reference type)
Where T : new()The type parameter <T> must have a default constructor. Note that this constraint must be listed last on a multiconstrained type
where T : NameOfBaseClassThe type parameter <T> must be derived from the class specified by NameOfBaseClass
where T : NameOfInterfaceThe type parameter <T> must implement the interface specified by NameOfInterface. You can separate multiple interfaces as a comma delimited list

 

    public class MyGeneric<T> where T : new(){}

    public class MyGeneric1<T> where T : MainClass, IComparable, new(){}

    public class MyGeneric2<T, K> where T : new() where K : struct, IComparable<T>{}

 

转载于:https://www.cnblogs.com/timBo/p/4370239.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值