You need a linked data structure that allows you to easily add and remove elements.
Solution
Use the generic LinkedList<T>class. The following method creates a LinkedList<T>class, adds nodes to this linked list object, and then uses several methods to obtain information from nodes within the linked list:
public static void UseLinkedList() { Console.WriteLine("\r\n\r\n");
// Create TodoItem objects to add to the linked list TodoItem i1 = new TodoItem() { Name = "paint door", Comment = "Should be done third" }; TodoItem i2 = new TodoItem() { Name = "buy door", Comment = "Should be done first" }; TodoItem i3 = new TodoItem() { Name = "assemble door", Comment = "Should be done second" }; TodoItem i4 = new TodoItem() { Name = "hang door", Comment = "Should be done last" };
// Create a new LinkedList object LinkedList<TodoItem>todoList = new LinkedList<TodoItem>();
// Add the items todoList.AddFirst(i1); todoList.AddFirst(i2); todoList.AddBefore(todoList.Find(i1), i3); todoList.AddAfter(todoList.Find(i1), i4);
// Display all items foreach (TodoItem tdi in todoList) { Console.WriteLine(tdi.Name + " : " + tdi.Comment); }
// Display information from the first node in the linked list Console.WriteLine("todoList.First.Value.Name == " + todoList.First.Value.Name);
// Display information from the second node in the linked list Console.WriteLine("todoList.First.Next.Value.Name == " + todoList.First.Next.Value.Name);
// Display information from the next to last node in the linked list Console.WriteLine("todoList.Last.Previous.Value.Name == " + todoList.Last.Previous.Value.Name); }
The output for this method is shown here:
buy door : Should be done first assemble door : Should be done second paint door : Should be done third hang door : Should be done last todoList.First.Value.Name == buy door todoList.First.Next.Value.Name == assemble door todoList.Last.Previous.Value.Name == paint door
This is theTodoItemclass, which is a simple container of two string propertiesNameandComment. The properties use the new Automatically Implemented Properties feature in C# 3.0 that allows you to declare properties, and the definition of the backing fields is generated automatically:
/// <summary> /// Todo list item /// </summary> public class TodoItem {
/// <summary> /// Name of the item /// </summary> public string Name { get; set; }
/// <summary> /// Comment for the item /// </summary> public string Comment { get; set; } }
Discussion
The LinkedList<T>class in the .NET Framework is a doubly linked list. This is because each node in the linked list contains a pointer to both the previous node and the next node in the linked list. Figure 4-1 shows what a doubly linked list looks like diagrammed on paper. Each node in this diagram represents a single LinkedListNode<T>object.
Figure 4-1. Graphical representation of a doubly linked list with three nodes
Notice that each node (i.e., the square boxes) contains a reference to the next node (i.e., the arrows pointing to the right) and a pointer to the previous node (i.e., the arrows pointing to the left) in the linked list. In contrast, a singly linked list contains only pointers to the next node in the list. There is no pointer to the previous node.
In theLinkedList<T>class, the previous node is always accessed through thePrevious property, and the next node is always accessed through theNextproperty. The first node’sPreviousproperty in the linked list always returns anull value. Likewise, the last node’sNextproperty in the linked list always returns anullvalue.
Each node (represented by the boxes in Figure 4-1) in the linked list is actually a genericLinkedListNode<T>object. So aLinkedList<T>object is actually a collection ofLinkedListNode<T>objects. Each of theseLinkedListNode<T>objects contains properties to access the next and previousLinkedListNode<T>objects, as well as the object contained within it. The object contained in theLinkedListNode<T>object is accessed through theValueproperty. In addition to these properties, aLinkedListNode<T>object also contains a property calledList, which allows access to the containingLinkedList<T>object.
Items to be aware of withList<T>andLinkedList<T>:
Adding and removing nodes within aList<T>is, in general, faster than the same operation using aLinkedList<T>class.
AList<T>stores its data essentially in one big array on the managed heap, whereas theLinkedList<T>can potentially store its nodes all over the managed heap. This forces the garbage collector to work that much harder to manageLinkedList<T>node objects on the managed heap.
Note that theList<T>.Insert* methods can be slower than adding a node anywhere within aLinkedList<T>using one of itsAdd* methods. However, this is dependent on where the object is inserted into theList<T>. AnInsertmethod must shift all the elements within theList<T>object at the point where the new element is inserted up by one position. If the new element is inserted at or near the end of theList<T>, the overhead of shifting the existing elements is negligible compared to the garbage collector overhead of managing theLinkedList<T>nodes objects. Another area where theList<T>can outperform theLinkedList<T>is when you’re doing an indexed access. With theList<T>, you can use the indexer to do an indexed lookup of the element at the specified position. However, with aLinkedList<T>class, you do not have that luxury. With aLinkedList<T>class, you must navigate theLinkedListNode<T>objects using thePreviousandNextproperties on eachLinkedListNode<T>, running through the list until you find the one at the specified position.
AList<T>class also has performance benefits over aLinkedList<T>class when searching for an element or node. TheList<T>.BinarySearchmethod is faster at finding elements within aList<T>object than its comparable methods within theLinkedList<T>class, namely theContains,Find, andFindLast methods.
Table 4-2 shows the comparison betweenList<T>andLinkedList<T>.
Table 4-2. Performance comparison between List<T>and LinkedList<T>
Action
Who Wins
Adding/Removing Nodes
List
Inserting nodes
LinkedList*
Indexed access
List
Node searching
List
See Also
The “LinkedListClass” topic in the MSDN documentation.
You have a variable that is a numeric type, which will hold a numeric value obtained from a database. The database may return this value as a null. You need a simple, clean way to store this numeric value, even if it is returned as a null.
Solution
Use a nullable value type. There are two ways of creating a nullable value type. The first way is to use the ? type modifier:
int? myDBInt = null;
The second way is to use theNullable<T>generic type:
Nullable<int> myDBInt = new Nullable<int>();
Discussion
Both of the following statements are equivalent:
int? myDBInt = null; Nullable<int> myDBInt = new Nullable<int>();
In both cases,myDBIntis a nullable type and is initialized tonull.
A nullable type implements theINullableValueinterface, which has two read-only property members,HasValueandValue. TheHasValueproperty returnsfalseif the nullable value is set tonull; otherwise, it returnstrue. IfHasValuereturnstrue, you can access theValue property, which contains the currently stored value. IfHasValue returnsfalseand you attempt to read theValueproperty, you will get anInvalidOperationExceptionthrown. This is because theValueproperty is undefined at this point. Below is an example of a test of nullable value using theHasValue property value:
if (myDBInt.HasValue) Console.WriteLine("Has a value: " + myDBInt.Value); else Console.WriteLine("Does not have a value (NULL)");
In addition, one can simply compare the value tonull, as shown below:
if (myDBInt != null) Console.WriteLine("Has a value: " + myDBInt.Value); else Console.WriteLine("Does not have a value (NULL)");
Either method is acceptable.
When casting a nullable value to a non-nullable value, the cast operates as it would normally, except when the nullable type is set tonull. In this case, anInvalidOperationExceptionis thrown. When casting a non-nullable value to a nullable value, the cast operates as it would normally. NoInvalidOperationExceptionwill be thrown, as the non-nullable value can never benull.
The tricky thing to watch out for with nullable types is when comparisons are performed. For example, if the following code is executed:
The text “myTempDBInt >= 100” is displayed, which is obviously incorrect if the value ofmyTempDBInt isnull. To fix this code, you have to check ifmyTempDBIntisnull. If it is not, you can execute theifstatement in the previous code block:
if (myTempDBInt != null) { if (myTempDBInt < 100) Console.WriteLine("myTempDBInt < 100"); else Console.WriteLine("myTempDBInt >= 100"); } else { // Handle the null here. }
Another interesting thing about nullable types is that you can use them in expressions similar to normal numeric types, for example:
int? DBInt = 10; int Value = 2; int? Result = DBInt + Value; // Result == 12
The result of using a nullable value in most operators is anull if any nullable value isnull.
Neither the comparison operators nor the null coalescing operator lift to nullable.
However, if none of the nullable values isnull, the operation is evaluated as it normally would be. IfDBInt, for example, were set tonull, the value placed inResult would also benull.
See Also
The “Nullable<T>Generic Class” and “Using Nullable Types” topics in the MSDN documentation.
You want to be able to reverse the contents of a sorted list of items while also maintaining the ability to access them in both array and list styles like SortedList and the generic SortedList<T>classes provide. Neither SortedList nor SortedList<T>provides a direct way to accomplish this without reloading the list.
Solution
Use LINQ to Objects to query the SortedList<T>and apply a descending order to the information in the list. After instantiating a SortedList<TKey, TValue>, the key of which is an int and the value of which is astring, a series of unordered numbers and their text representations are inserted into the list. Those items are then displayed:
SortedList<int, string> data = new SortedList<int, string>(); data.Add(2, "two"); data.Add(5, "five"); data.Add(3, "three"); data.Add(1, "one");
The output for the list is shown sorted in ascending order (the default):
1 one 2 two 3 three 5 five
Now the sort order is reversed by creating a query using LINQ to Objects and setting theorderbyclause todescending. The results are then displayed from the query result set:
// query ordering by descending var query = from d in data orderby d.Key descending select d;
When a new item is added to the list, it is added in the ascending sort order, but by querying again after adding all of the items, you keep the ordering of the list intact:
data.Add(4, "four");
// requery ordering by descending query = from d in data orderby d.Key descending select d;
// Just go against the original list for ascending foreach (KeyValuePair<int, string> kvp in data) { Debug.WriteLine("\t" + kvp.Key + "\t" + kvp.Value); }
It can be seen that the output has both descending and ascending orders with the new item:
5 five 4 four 3 three 2 two 1 one 1 one 2 two 3 three 4 four 5 five
Discussion
A SortedList blends array and list syntax to allow for accessing the data in either format, which can be a handy thing to do. The data is accessible as key/value pairs or directly by index and will not allow duplicate keys to be added. In addition, values that are reference or nullable types can be null, but keys cannot. The items can be iterated using a foreach loop, withKeyValuePairbeing the type returned. While accessing elements of theSortedList<T>, they may only be read from. The usual iterator syntax prohibits updating or deleting elements of the list while reading, as it will invalidate the iterator.
Theorderby clause in the query causes the result set of the query to be ordered either inascending(the default) ordescendingorder. This sorting is accomplished through use of the default comparer for the element type, so it can be affected by overriding theEqualsmethod for elements that are custom classes. Multiple keys can be specified for theorderbyclause, which has the effect of nesting the sort order such as sorting by “last name” and then “first name.”
See Also
The “SortedList,” “Generic KeyValuePair Structure,” and “Generic SortedList” topics in the MSDN documentation.
You have a collection of information that you want to expose from your class, but you don’t want any users modifying the collection.
Solution
Use the ReadOnlyCollection<T>wrapper to easily support collection classes that cannot be modified. For example, a Lottery class that contained the winning lottery numbers should make the winning numbers accessible but not allow them to be changed:
public class Lottery { // make a list List<int> _numbers = null;
public Lottery() { // pick the winning numbers _numbers = new List<int>(5) { 17, 21, 32, 44, 58 }; }
public ReadOnlyCollection<int> Results { // return a wrapped copy of the results get { return new ReadOnlyCollection<int>(_numbers); } } }
Lotteryhas aList<int>of winning numbers that it fills in the constructor. The interesting part is that it also exposes a property calledResults, which returns aReadOnlyCollectiontyped as<int>for seeing the winning numbers. Internally, a newReadOnlyCollection wrapper is created to hold theList<int>that has the numbers in it, and then this instance is returned for use by the user.
If users then attempt to set a value on the collection, they get a compile error:
Lottery tryYourLuck = new Lottery(); // Print out the results. for (int i = 0; i < tryYourLuck.Results.Count; i++) { Console.WriteLine("Lottery Number " + i + " is " + tryYourLuck.Results[i]); }
// Change it so we win! tryYourLuck.Results[0]=29;
//The above line gives // Error 26 // Property or indexer // 'System.Collections.ObjectModel. ReadOnlyCollection<int>.this[int]' // cannot be assigned to -- it is read only
Discussion
The main advantage ReadOnlyCollection provides is the flexibility to use it with any collection that supports IList or IList<T>as an interface. ReadOnlyCollection can be used to wrap a regular array like this:
This provides a way to standardize the read-only properties on classes to make it easier for consumers of the class to recognize which properties are read-only simply by the return type.
See Also
The “ReadOnlyCollection” topic in the MSDN documentation.
Please check back next week for the conclusion of this article.