Chapter 2. Collections, Enumerators, and Iterators

2.0 Introduction

Collections are groups of items; in .NET, collections contain objects, and each object contained in a collection is called an element. Some collections contain a straightforward list of elements, while others (dictionaries) contain a list of key/value pairs. The following collection types consist of a straightforward list of elements:

System.Collections.ArrayList
System.Collections.BitArray
System.Collections.Queue
System.Collections.Stack
System.Collections.Generic.LinkedList<T>
System.Collections.Generic.List<T>
System.Collections.Generic.Queue<T>
System.Collections.Generic.Stack<T>
System.Collections.Generic.HashSet<T>

The next set of collection types are all dictionaries:

System.Collections.Hashtable
System.Collections.SortedList
System.Collections.Generic.Dictionary<T,U>
System.Collections.Generic.SortedList<T,U>

This last collection type (HashSet<T>) can be thought of as a list of elements with no duplicates:

System.Collections.Generic.HashSet<T>

These collection classes are organized under the System.Collections and the System. Collections.Generic namespaces. In addition to these namespaces, there is a namespace called System.Collections.Specialized, which contains a few more useful collection classes. These classes might not be as well known as the previous classes, so here is a short explanation of them:

ListDictionary
This class operates similarly to the Hashtable. However, this class beats out the Hashtable on performance when it contains 10 or fewer elements.
HybridDictionary
This class consists of two internal collections, the ListDictionary and the Hashtable. Only one of these classes is used at any time. The ListDictionary is used while the collection contains 10 or fewer elements, and then a Hashtable is used once the collection grows beyond 10 elements. This switch is made transparently to the developer. Once the Hashtable is used, the collection cannot revert to using the ListDictionary even if the elements number 10 or fewer. Also note that, when you’re using strings as the key, this class supports both case-sensitive (with respect to the invariant culture) and case-insensitive string searches through a Boolean value you set in the constructor.
CollectionsUtil
This class contains two static methods: one to create a case-insensitive Hashtable and another to create a case-insensitive SortedList. When you directly create a Hashtable and SortedList object, you always create a case-sensitive Hashtable or SortedList, unless you use one of the constructors that takes an IComparer and pass CaseInsensitiveComparer.Default to it.
NameValueCollection
This collection consists of key/value pairs in which both the key and the value are of type String. The interesting thing about this collection is that it can store multiple string values with a single key. The multiple string values are comma-delimited. The String.Split method is useful for breaking up multiple strings in a value.
StringCollection
This collection is a simple list containing string elements. This list accepts null elements as well as duplicate strings. This list is case-sensitive.
StringDictionary
This is a Hashtable that stores both the key and value as strings. Keys are converted to all-lowercase letters before being added to the Hashtable, allowing for case-insensitive comparisons. Keys cannot be null, but values may be set to null.

The C# compiler also supports a fixed-size array. You can create arrays of any type using the following syntax:

int[] foo = new int[2];
T[] bar = new T[2];

Here, foo is an integer array containing exactly two elements, and bar is an array of unknown type T.

Arrays come in several styles as well: single-dimensional, jagged, and even jagged multidimensional. Multidimensional arrays are defined here:

int[,] foo = new int[2,3];      // A 2-dimensional array
                                // containing 6 elements

int[,,] bar = new int[2,3,4];   // A 3-dimensional array
                                // containing 24 elements

A two-dimensional array is usually described as a table with rows and columns. The foo array would be described as a table of two rows, each containing three columns of elements. A three-dimensional array can be described as a cube with layers of tables. The bar array could be described as four layers of two rows, each containing three columns of elements.

Jagged arrays are arrays of arrays. If you picture a jagged array as a one-dimensional array with each element in that array containing another one-dimensional array, it could have a different number of elements in each row. A jagged array is defined as follows:

int[][] baz = new int[2][] {new int[2], new int[3]};

The baz array consists of a one-dimensional array containing two elements. Each of these elements consists of another array, the first array having two elements and the second array having three.

When dealing with collections, you will likely need to examine all of the values in a collection at some point. To help you accomplish this, C# provides the iterator and enumerator constructs. Iterators allow for a block of code to yield an ordered sequence of values, while enumerators support the iteration over data sets and can be used to read data in a collection but not modify it.

Iterators are a mechanism for producing data that can be iterated over by the foreach loop construct. However, iterators are much more flexible than this. You can easily generate a sequence of data returned by the enumerator (known as lazy computation); it does not have to be hardcoded up front (as it does in eager computation). For example, you could easily write an enumerator that generates the Fibonacci sequence on demand. Another flexible feature of iterators is that you do not have to set a limit on the number of values returned by the iterator, so in this example, you could choose when to stop producing the Fibonacci sequence. This is an interesting distinction in the LINQ (Language Integrated Query) world. Iterators like the one produced by the IEnumerable version of where are lazy, but grouping or sorting requires eagerness.

Iterators allow you to hand off the work of writing this class to the C# compiler. Now, you need to add only an iterator to your type. An iterator is a member within your type (e.g., a method, an operator overload, or the get accessor of a property) that returns either a System.Collections.IEnumerator, a System.Collections.Generic.IEnumerator<T>, a System.Collections.IEnumerable, or a System.Collections.Generic.IEnumerable<T> and that contains at least one yield statement. This allows you to write types that can be used by foreach loops.

Iterators play an important role in LINQ, as LINQ to Objects is based on being able to work on classes that implement IEnumerable<T>. Iterators allow for the query engine to iterate over collections while performing the various query, projection, ordering, and grouping operations. Without iterator support, LINQ would be much more cumbersome, and the declarative style of programming that it brings would be clumsy, if not lost altogether.

2.1 Looking for Duplicate Items in a List<T>

Problem

You need to be able to either retrieve or count the number of occurrences of an object contained in a List<T> that matches a search criterion.

Solution

Use the four extension methods for List<T>: GetAll, BinarySearchGetAll, CountAll, and BinarySearchCountAll. These methods extend the List<T> class to return either instances of a particular object or the number of times a particular object appears in a sorted and an unsorted List<T>, as shown in Example 2-1.

Example 2-1. Determining the number of times an item appears in a List <T>
static class CollectionExtMethods
{
    #region 2.1 Looking for Duplicate Items in a List<T>

    // The method to retrieve all matching objects in a
    //  sorted or unsorted List<T>
    public static IEnumerable<T> GetAll<T>(this List<T> myList, T searchValue) =>
        myList.Where(t => t.Equals(searchValue));

    // The method to retrieve all matching objects in a sorted ListEx<T>
    public static T[] BinarySearchGetAll<T>(this List<T> myList, T searchValue)
    {
        List<T> retObjs = new List<T>();

        // Search for first item.
        int center = myList.BinarySearch(searchValue);
        if (center > 0)
        {
            retObjs.Add(myList[center]);

            int left = center;
            while (left > 0 && myList[left - 1].Equals(searchValue))
            {
                left -= 1;
                retObjs.Add(myList[left]);
            }

            int right = center;
            while (right < (myList.Count - 1) &&
                myList[right + 1].Equals(searchValue))
            {
                right += 1;
                retObjs.Add(myList[right]);
            }
        }

        return (retObjs.ToArray());
    }
    // Count the number of times an item appears in this
    //   unsorted or sorted List<T>
    public static int CountAll<T>(this List<T> myList, T searchValue) =>
        myList.GetAll(searchValue).Count();

    // Count the number of times an item appears in this sorted List<T>
    public static int BinarySearchCountAll<T>(this List<T> myList, T searchValue) =>
        BinarySearchGetAll(myList, searchValue).Count();
    #endregion // 2.1
}

Discussion

The GetAll and BinarySearchGetAll methods return the actual items found in a List<T> object. The CountAll and BinarySearchCountAll methods leverage GetAll and BinarySearchGetAll to provide the count of the items. The main thing to keep in mind when choosing between GetAll and BinarySearchGetAll is whether you are going to be looking at a List<T> that is sorted or unsorted. Choose the GetAll and CountAll methods to obtain either an array of all found items (GetAll) or the number of found items (CountAll) from an unsorted List<T>, and choose the BinarySearchGetAll and BinarySearchCountAll methods to work with a sorted List<T>. GetAll, SearchAll, and BinarySearchAll use the expression-bodied member syntax, as they are simple functions.

The following code demonstrates these two new extension methods of the List<T> class:

// Retrieval
List<int> listRetrieval =
    new List<int>() { -1, -1, 1, 2, 2, 2, 2, 3, 100, 4, 5 };

Console.WriteLine("--GET All--");
IEnumerable<int> items = listRetrieval.GetAll(2);
foreach (var item in items)
    Console.WriteLine($"item: {item}");

Console.WriteLine();
items = listRetrieval.GetAll(-2);
foreach (var item in items)
    Console.WriteLine($"item-2: {item}");

Console.WriteLine();
items = listRetrieval.GetAll(5);
foreach (var item in items)
    Console.WriteLine($"item5: {item}");

Console.WriteLine("
--BINARY SEARCH GET ALL--");
listRetrieval.Sort();
int[] listItems = listRetrieval.BinarySearchGetAll(-2);
foreach (var item in listItems)
    Console.WriteLine($"item-2: {item}");

Console.WriteLine();
listItems = listRetrieval.BinarySearchGetAll(2);
foreach (var item in listItems)
    Console.WriteLine($"item2: {item}");

Console.WriteLine();
listItems = listRetrieval.BinarySearchGetAll(5);
foreach (var item in listItems)
    Console.WriteLine($"item5: {item}");

This code outputs the following:

--GET All--
item: 2
item: 2
item: 2
item: 2


item5: 5

--BINARY SEARCH GET ALL--

item2: 2
item2: 2
item2: 2
item2: 2

item5: 5

The BinarySearchGetAll method is faster than the GetAll method, especially if the array has already been sorted. If a BinarySearch is used on an unsorted List<T>, the results returned by the search will be incorrect, as it has been consistently documented as a requirement that List<T> be sorted.

The CountAll method accepts a search value (searchValue) of generic type T. CountAll then proceeds to count the number of times the search value appears in the List<T> class by using the GetAll extension method to get the items and calling Count on the result. This method may be used when the List<T> is sorted or unsorted. If the List<T> is sorted (you sort a List<T> by calling the Sort method), you can use the BinarySearchCountAll method to increase the efficiency of the search. You do so by using the BinarySearchGetAll extension method on the List<T> class, which is much faster than iterating through the entire List<T>. This is especially true as the List<T> grows in size.

The following code illustrates these two new methods of the List<T> class:

List<int> list = new List<int>() {-2,-2,-1,-1,1,2,2,2,2,3,100,4,5};

Console.WriteLine("--CONTAINS TOTAL--");
int count = list.CountAll(2);
Console.WriteLine($"Count2: {count}");

count = list.CountAll(3);
Console.WriteLine($"Count3: {count}");

count = list.CountAll(1);
Console.WriteLine($"Count1: {count}");

Console.WriteLine("
--BINARY SEARCH COUNT ALL--");
list.Sort();
count = list.BinarySearchCountAll(2);
Console.WriteLine($"Count2: {count}");

count = list.BinarySearchCountAll(3);
Console.WriteLine($"Count3: {count}");

count = list.BinarySearchCountAll(1);
Console.WriteLine($"Count1: {count}");

This code outputs the following:

--CONTAINS TOTAL--
Count2: 4
Count3: 1
Count1: 1

--BINARY SEARCH COUNT ALL--
Count2: 4
Count3: 1
Count1: 1

The CountAll and GetAll methods use a sequential search that is performed in a for loop. A linear search must be used since the List<T> is not assumed to be sorted. The where statement determines whether each element in the List<T> is equal to the search criterion (searchValue). The items or count of items are returned by these methods to indicate the number of items matching the search criteria in the List<T>.

The BinarySearchGetAll method implements a binary search to locate an item matching the search criteria (searchValue) in the List<T>. If one is found, a while loop is used to find the very first matching item in the sorted List<T>, and the position of that element is recorded in the left variable. A second while loop is used to find the very last matching item, and the position of this element is recorded in the right variable. The value in the left variable is subtracted from the value in the right variable, and then 1 is added to this result in order to get the total number of matches. BinarySearchCountAll uses BinarySearchGetAll to get the items and then just calls Count on the resulting set.

See Also

The “List<T> Class” topic in the MSDN documentation.

2.2 Keeping Your List<T> Sorted

Problem

You will be using the BinarySearch method of the List<T> to periodically search the List<T> for specific elements. The addition, modification, and removal of elements will be interleaved with the searches. The BinarySearch method, however, presupposes a sorted array; if the List<T> is not sorted, the BinarySearch method will possibly return incorrect results. You do not want to have to remember to always call the List<T>.Sort method before calling the List<T>.BinarySearch method, not to mention incurring all the overhead associated with this call. You need a way of keeping the List<T> sorted without always having to call the List<T>.Sort method.

Solution

The following SortedList generic class enhances the addition and modification of elements within a List<T>. These methods keep the array sorted when items are added to it and modified. Note that a DeleteSorted method is not required because deleting an item does not disturb the sorted order of the remaining items:

public class SortedList<T> : List<T>
{
    public new void Add(T item)
    {
        int position = this.BinarySearch(item);
        if (position < 0)
            position = ~position;

        this.Insert(position, item);
    }

    public void ModifySorted(T item, int index)
    {
        this.RemoveAt(index);

        int position = this.BinarySearch(item);
        if (position < 0)
            position = ~position;

        this.Insert(position, item);
    }
}

Discussion

Use the Add method to add elements while keeping the List<T> sorted. The Add method accepts a generic type (T) to add to the sorted list.

Instead of using the List<T> indexer directly to modify elements, use the ModifySorted method to modify elements while keeping the List<T> sorted. Call this method, passing in the generic type T to replace the existing object (item) and the index of the object to modify (index).

The following code demonstrates the SortedList<T> class:

// Create a SortedList and populate it with
//    randomly chosen numbers
SortedList<int> sortedList = new SortedList<int>();
sortedList.Add(200);
sortedList.Add(20);
sortedList.Add(2);
sortedList.Add(7);
sortedList.Add(10);
sortedList.Add(0);
sortedList.Add(100);
sortedList.Add(-20);
sortedList.Add(56);
sortedList.Add(55);
sortedList.Add(57);
sortedList.Add(200);
sortedList.Add(-2);
sortedList.Add(-20);
sortedList.Add(55);
sortedList.Add(55);

// Display it
foreach (var i in sortedList)
    Console.WriteLine(i);

// Now modify a value at a particular index
sortedList.ModifySorted(0, 5);
sortedList.ModifySorted(1, 10);
sortedList.ModifySorted(2, 11);
sortedList.ModifySorted(3, 7);
sortedList.ModifySorted(4, 2);
sortedList.ModifySorted(2, 4);
sortedList.ModifySorted(15, 0);
sortedList.ModifySorted(0, 15);
sortedList.ModifySorted(223, 15);

// Display it
Console.WriteLine();
foreach (var i in sortedList)
    Console.WriteLine(i);

This method automatically places the new item in the List<T> while keeping its sort order; it does so without your having to explicitly call List<T>.Sort. The reason this works is because the Add method first calls the BinarySearch method and passes it the object to be added to the ArrayList. The BinarySearch method will either return the index where it found an identical item or a negative number that you can use to determine where the item that you are looking for should be located. If the BinarySearch method returns a positive number, you can use the List<T>.Insert method to insert the new element at that location, keeping the sort order within the List<T>. If the BinarySearch method returns a negative number, you can use the bitwise complement operator ~ to determine where the item should have been located, had it existed in the sorted list. Using this number, you can use the List<T>.Insert method to add the item to the correct location in the sorted list while keeping the correct sort order.

You can remove an element from the sorted list without disturbing the sort order, but modifying an element’s value in the List<T> most likely will cause the sorted list to become unsorted. The ModifySorted method alleviates this problem. This method works similarly to the Add method, except that it will initially remove the element from the List<T> and then insert the new element into the correct location.

See Also

The “List<T> Class” topic in the MSDN documentation.

2.3 Sorting a Dictionary’s Keys and/or Values

Problem

You want to sort the keys and/or values contained in a Dictionary in order to display the entire Dictionary to the user, sorted in either ascending or descending order.

Solution

Use a LINQ query and the Keys and Values properties of a Dictionary<T,U> object to obtain a sorted ICollection of its key and value objects. (See Chapter 4 for more on LINQ). The code shown here displays the keys and values of a Dictionary<T,U> sorted in ascending or descending order:

// Define a Dictionary<T,U> object
Dictionary<string, string> hash = new Dictionary<string, string>()
{
    ["2"] = "two",
    ["1"] = "one",
    ["5"] = "five",
    ["4"] = "four",
    ["3"] = "three"
};

var x = from k in hash.Keys orderby k ascending select k;
foreach (string s in x)
    Console.WriteLine($"Key: {s}  Value: {hash[s]}");

x = from k in hash.Keys orderby k descending select k;
foreach (string s in x)
    Console.WriteLine($"Key: {s}  Value: {hash[s]}");

The code shown here displays the values in a Dictionary<T,U> sorted in ascending or descending order:

x = from k in hash.Values orderby k ascending select k;
foreach (string s in x)
    Console.WriteLine($"Value: {s}");

Console.WriteLine();

x = from k in hash.Values orderby k descending select k;
foreach (string s in x)
    Console.WriteLine($"Value: {s}");

Discussion

The Dictionary<T,U> object exposes two useful properties for obtaining a collection of its keys or values. The Keys property returns an ICollection containing all the keys currently in the Dictionary<T,U>. The Values property returns the same for all values currently contained in the Dictionary<T,U>.

The ICollection object returned from either the Keys or Values property of a Dictionary<T,U> object contains direct references to the key and value collections within the Dictionary<T,U>. This means that if the keys and/or values change in a Dictionary<T,U>, the key and value collections will be altered accordingly.

Note that you can also use the SortedDictionary<T,U> class, which will automatically keep the keys sorted for you. You can use the constructor overload of SortedDictionary<T,U> to wrap an existing Dictionary<T,U> as well. The Keys property is in ascending order by default, so if you want descending instead you will need to sort the collection for descending order based on Keys:

SortedDictionary<string, string> sortedHash =
    new SortedDictionary<string, string>()
{
    ["2"] = "two",
    ["1"] = "one",
    ["5"] = "five",
    ["4"] = "four",
    ["3"] = "three"
};
foreach (string key in sortedHash.Keys)
    Console.WriteLine($"Key: {key}  Value: {sortedHash[key]}");
foreach (string key in sortedHash.OrderByDescending(item =>
    item.Key).Select(item => item.Key))
    Console.WriteLine($"Key: {key}  Value: {sortedHash[key]}");

Why would someone choose the LINQ solution shown versus just using SortedDictionary<T,U>? It is actually faster to perform the ordering in the LINQ query, and the code is cleaner than the code using the SortedDictionary<T,U>, so that is the recommended approach for all versions of .NET that support LINQ (3.0 and greater). If your solution happens to be on an older version of .NET, you still can use SortedDictionary<T,U> to accomplish the result.

See Also

The “Dictionary<T,U> Class,” “SortedDictionary<T,U> Class,” and “List<T> Class” topics in the MSDN documentation.

2.4 Creating a Dictionary with Min and Max Value Boundaries

Problem

You need to use a generic Dictionary object in your project that stores only numeric data in its value (the key can be of any type) between a set, predefined maximum and minimum value.

Solution

Create a class with accessors and methods that enforce these boundaries. The class shown in Example 2-2, MinMaxValueDictionary, allows only types to be stored that implement the IComparable interface and fall between a maximum and minimum value.

Example 2-2. Creating a dictionary with min and max value boundaries
[Serializable]
public class MinMaxValueDictionary<T, U>
    where U : IComparable<U>
{
    protected Dictionary<T, U> internalDictionary = null;

    public MinMaxValueDictionary(U minValue, U maxValue)
    {
        this.MinValue = minValue;
        this.MaxValue = maxValue;
        internalDictionary = new Dictionary<T, U>();
    }

    public U MinValue { get; private set; } = default(U);
    public U MaxValue { get; private set; } = default(U);

    public int Count => (internalDictionary.Count);

    public Dictionary<T, U>.KeyCollection Keys => (internalDictionary.Keys);

    public Dictionary<T, U>.ValueCollection Values => (internalDictionary.Values);

    public U this[T key]
    {
        get { return (internalDictionary[key]); }
        set
        {
            if (value.CompareTo(MinValue) >= 0 &&
                value.CompareTo(MaxValue) <= 0)
                internalDictionary[key] = value;
            else
                throw new ArgumentOutOfRangeException(nameof(value), value,
                    $"Value must be within the range {MinValue} to {MaxValue}");
        }
    }

    public void Add(T key, U value)
    {
        if (value.CompareTo(MinValue) >= 0 &&
            value.CompareTo(MaxValue) <= 0)
            internalDictionary.Add(key, value);
        else
            throw new ArgumentOutOfRangeException(nameof(value), value,
                $"Value must be within the range {MinValue} to {MaxValue}");
    }

    public bool ContainsKey(T key) => (internalDictionary.ContainsKey(key));

    public bool ContainsValue(U value) => (internalDictionary.ContainsValue(value));

    public override bool Equals(object obj) => (internalDictionary.Equals(obj));

    public IEnumerator GetEnumerator() => (internalDictionary.GetEnumerator());

    public override int GetHashCode() => (internalDictionary.GetHashCode());

    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        internalDictionary.GetObjectData(info, context);
    }

    public void OnDeserialization(object sender)
    {
        internalDictionary.OnDeserialization(sender);
    }

    public override string ToString() => (internalDictionary.ToString());

    public bool TryGetValue(T key, out U value) =>
          (internalDictionary.TryGetValue(key, out value));

    public void Remove(T key)
    {
        internalDictionary.Remove(key);
    }

    public void Clear()
    {
        internalDictionary.Clear();
    }
}

Discussion

The MinMaxValueDictionary class wraps the Dictionary<T,U> class, so it can restrict the range of allowed values. Defined here is the overloaded constructor for the MinMaxValueDictionary class:

public MinMaxValueDictionary(U minValue, U maxValue)

This constructor allows the range of values to be set. Its parameters are:

minValue
The smallest value of type U that can be added as a value in a key/value pair.
maxValue
The largest value of type U that can be added as a value in a key/value pair.

These values are available on the MinMaxValueDictionary<T,U> as the properties MinValue and MaxValue.

The overridden indexer has both get and set. The get accessor returns the value that matches the provided key. The set accessor checks the value parameter to determine whether it is within the boundaries of the minValue and maxValue fields before it is set.

The Add method accepts a type U for its value parameter and performs the same tests as the set accessor on the indexer. If the test passes, the integer is added to the MinMaxValueDictionary.

See Also

The “Dictionary<T, U> Class” topics in the MSDN documentation.

2.5 Persisting a Collection Between Application Sessions

Problem

You have a collection such as an ArrayList, List<T>, Hashtable, or Dictionary<T,U> in which you are storing application information. You can use this information to tailor the application’s environment to the last known settings (e.g., window size, window placement, and currently displayed toolbars). You can also use it to allow the user to start the application at the same point where it was last shut down. In other words, if the user is editing an invoice and needs to shut down the computer for the night, the application will know exactly which invoice to initially display when it is started again.

Solution

Serialize the object(s) to and from a file:

public static void SerializeToFile<T>(T obj, string dataFile)
{
    using (FileStream fileStream = File.Create(dataFile))
    {
        BinaryFormatter binSerializer = new BinaryFormatter();
        binSerializer.Serialize(fileStream, obj);
    }
}

public static T DeserializeFromFile<T>(string dataFile)
{
    T obj = default(T);
    using (FileStream fileStream = File.OpenRead(dataFile))
    {
        BinaryFormatter binSerializer = new BinaryFormatter();
        obj = (T)binSerializer.Deserialize(fileStream);
    }
    return obj;
}

Discussion

The dataFile parameter accepts a string value to use as a filename. The SerializeToFile<T> method accepts an object and attempts to serialize it to a file. Conversely, the DeserializeFromFile<T> method removes the serialized object from the file created in the SaveObj<T> method.

Example 2-3 shows how to use these methods to serialize an ArrayList object (note that this will work for any type that is marked with the SerializableAttribute).

Example 2-3. Persisting a collection between application sessions
ArrayList HT = new ArrayList() {"Zero","One","Two"};

foreach (object O in HT)
    Console.WriteLine(O.ToString());
SerializeToFile<ArrayList>(HT, "HT.data");

ArrayList HTNew = new ArrayList();
HTNew = DeserializeFromFile<ArrayList>("HT.data");
foreach (object O in HTNew)
    Console.WriteLine(O.ToString());

If you serialize your objects to disk at specific points in your application, you can then deserialize them and return to a known state—for instance, in the event of an unintended shutdown.

You could also serialize the object(s) to and from a byte stream for storage in isolated storage or remote storage:

public static byte[] Serialize<T>(T obj)
{
    using (MemoryStream memStream = new MemoryStream())
    {
        BinaryFormatter binSerializer = new BinaryFormatter();
        binSerializer.Serialize(memStream, obj);
        return memStream.ToArray();
    }
}

public static T Deserialize<T>(byte[] serializedObj)
{
    T obj = default(T);
    using (MemoryStream memStream = new MemoryStream(serializedObj))
    {
        BinaryFormatter binSerializer = new BinaryFormatter();
        obj = (T)binSerializer.Deserialize(memStream);
    }
    return obj;
}
Note

If you rely on serialized objects to store persistent information, you need to figure out what you are going to do when you deploy a new version of the application. You should plan ahead with either a strategy for making sure the types you serialize don’t get changed or a technique for dealing with changes. Otherwise, you are going to have big problems when you deploy an update. Check out the “Version Tolerant Serialization” article in MSDN for ideas and best practices on handling this situation.

See Also

The “ArrayList Class,” “Hashtable Class,” “List<T> Class,” “Dictionary<T,U> Class,” “File Class,” “Version Tolerant Serialization,” and “BinaryFormatter Class” topics in the MSDN documentation.

2.6 Testing Every Element in an Array or List<T>

Problem

You need an easy way to test every element in an Array or List<T>. The results of this test should indicate that the test passed for all elements in the collection, or it failed for at least one element in the collection.

Solution

Use the TrueForAll method, as shown here:

// Create a List of strings
List<string> strings = new List<string>() {"one",null,"three","four"};

// Determine if there are no null values in the List
string str = strings.TrueForAll(delegate(string val)
{
    if (val == null)
        return false;
    else
        return true;
}).ToString();

// Display the results
Console.WriteLine(str);

Discussion

The addition of the TrueForAll method on the Array and List<T> classes allows you to easily set up tests for all elements in these collections. The code in the Solution for this recipe tests all elements to determine if any are null. You could just as easily set up tests to determine, for example:

  • if any numeric elements are above a specified maximum value;

  • if any numeric elements are below a specified minimum value;

  • if any string elements contain a specified set of characters;

  • if any data objects have all of their fields filled in; and

  • any others you may come up with.

The TrueForAll method accepts a generic delegate Predicate<T> called match and returns a Boolean value:

public bool TrueForAll(Predicate<T> match)

The match parameter determines whether or not a true or false should be returned by the TrueForAll method.

The TrueForAll method basically consists of a loop that iterates over each element in the collection. Within this loop, a call to the match delegate is invoked. If this delegate returns true, the processing continues on to the next element in the collection. If this delegate returns false, processing stops and a false is returned by the TrueForAll method. When the TrueForAll method finishes iterating over all the elements of the collection and the match delegate has not returned a false value for any element, the TrueForAll method returns a true.

There is not a FalseForAll method, but you can reverse your logic and use TrueForAll to accomplish the same thing:

List<string> nulls = new List<string>() { null, null, null, null };

// Determine if there are all null values in the List
string result = nulls.TrueForAll(delegate (string val)
{
    if (val == null)
        return true;
    else
        return false;
}).ToString();

// Display the results
Console.WriteLine(result);

One other consideration here is that TrueForAll stops the first time the condition is not true. This means that not all nodes are checked. If you have an array of, say, file handles or resources that need to be processed or freed, you would need to iterate over all of them even if the action performed during the check fails. In this case you’d want to note if any of them failed, but it would still be important to visit each Array or List<T> element and perform the action.

See Also

The “Array Class,” “List<T> Class,” and “TrueForAll Method” topics in the MSDN documentation.

2.7 Creating Custom Enumerators

Problem

You need to add foreach support to a class, but the normal way of adding an iterator (i.e., implementing IEnumerable on a type and returning a reference to this IEnumerable from a member function) is not flexible enough. Instead of simply iterating from the first element to the last, you also need to iterate from the last to the first, and you need to be able to step over, or skip, a predefined number of elements on each iteration. You want to make all of these types of iterators available to your class.

Solution

The Container<T> class shown in Example 2-4 acts as a container for a private List<T> called internalList. Container is implemented so you can use it in a foreach loop to iterate through the private internalList.

Example 2-4. Creating custom iterators
public class Container<T> : IEnumerable<T>
{
    public Container() { }

    private List<T> _internalList = new List<T>();

    // This iterator iterates over each element from first to last
    public IEnumerator<T> GetEnumerator() => _internalList.GetEnumerator();

    // This iterator iterates over each element from last to first
    public IEnumerable<T> GetReverseOrderEnumerator()
    {
        foreach (T item in ((IEnumerable<T>)_internalList).Reverse())
            yield return item;
    }

    // This iterator iterates over each element from first to last, stepping
    // over a predefined number of elements
    public IEnumerable<T> GetForwardStepEnumerator(int step)
    {
        foreach (T item in _internalList.EveryNthItem(step))
            yield return item;
    }

    // This iterator iterates over each element from last to first, stepping
    // over a predefined number of elements
    public IEnumerable<T> GetReverseStepEnumerator(int step)
    {
        foreach (T item in (
            (IEnumerable<T>)_internalList).Reverse().EveryNthItem(step))
            yield return item;
    }

    #region IEnumerable Members

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

    #endregion

    public void Clear()
    {
        _internalList.Clear();
    }

    public void Add(T item)
    {
        _internalList.Add(item);
    }

    public void AddRange(ICollection<T> collection)
    {
        _internalList.AddRange(collection);
    }
}

Discussion

Iterators provide an easy method of moving from item to item within an object using the familiar foreach loop construct. The object can be an array, a collection, or some other type of container. This is similar to using a for loop to manually iterate over each item contained in an array. In fact, an iterator can be set up to use a for loop—or any other looping construct, for that matter—as the mechanism for yielding each item in the object. In fact, you do not even have to use a looping construct. The following code is perfectly valid:

public static IEnumerable<int> GetValues()
{
    yield return 10;
    yield return 20;
    yield return 30;
    yield return 100;
}

With the foreach loop, you do not have to worry about watching for the end of the list, since you cannot go beyond the bounds of the list. The best part about the foreach loop and iterators is that you do not have to know how to access the list of elements within its container. In fact, you do not even have to have access to the list of elements, as the iterator member(s) implemented on the container handle this for you.

To see what foreach is doing here, let’s look at code to iterate over the Container class:

// Iterate over Container object
foreach (int i in container)
    Console.WriteLine(i);

foreach will take the following actions while this code executes:

  1. Get the enumerator from the container using IEnumerator.GetEnumerator().

  2. Access the IEnumerator.Current property for the current object (int) and place it into i.

  3. Call IEnumerator.MoveNext(). If MoveNext returns true, go back to step 2, or else end the loop.

The Container class contains a private List of items called internalList. There are four iterator members within this class:

GetEnumerator
GetReverseOrderEnumerator
GetForwardStepEnumerator
GetReverseStepEnumerator

The GetEnumerator method iterates over each element in the internalList from the first to the last element. This iterator, similar to the others, uses a for loop to yield each element in the internalList.

The GetReverseOrderEnumerator method implements an iterator in its get accessor (set accessors cannot be iterators). This iterator is very similar in design to the GetEnumerator method, except that the foreach loop works on the internalList in the reverse direction by using the IEnumerable<T>.Reverse extension method. The last two iterators, GetForwardStepEnumerator and GetReverseStepEnumerator, are similar in design to GetEnumerator and GetReverseOrderEnumerator, respectively. The main difference is that the foreach loop uses the EveryNthItem extension method to skip over the specified number of items in the internalList:

public static IEnumerable<T> EveryNthItem<T>(this IEnumerable<T> enumerable,
    int step)
{
    int current = 0;
    foreach (T item in enumerable)
    {
        ++current;
        if (current % step == 0)
            yield return item;
    }
}

Notice also that only the GetEnumerator method must return an IEnumerator<T> interface; the other three iterators must return IEnumerable<T> interfaces.

To iterate over each element in the Container object from first to last, use the following code:

Container<int> container = new Container<int>();
    //...Add data to container here ...
foreach (int i in container)
    Console.WriteLine(i);

To iterate over each element in the Container object from last to first, use the following code:

Container<int> container = new Container<int>();
    //...Add data to container here ...
foreach (int i in container.GetReverseOrderEnumerator())
    Console.WriteLine(i);

To iterate over each element in the Container object from first to last while skipping every other element, use the following code:

Container<int> container = new Container<int>();
    //...Add data to container here ...
foreach (int i in container.GetForwardStepEnumerator(2))
    Console.WriteLine(i);

To iterate over each element in the Container object from last to first while skipping to every third element, use the following code:

Container<int> container = new Container<int>();
    //...Add data to container here ...
foreach (int i in container.GetReverseStepEnumerator(3))
    Console.WriteLine(i);

In each of the last two examples, the iterator method accepts an integer value, step, which determines how many items will be skipped.

One last note on yield: while it is technically possible to use yield inside of a lock statement (see the Discussion in Recipe 9.9 for more on lock), you should avoid doing so, as it could cause deadlocks in your application. The code you yield to could be taking out locks itself and causing the deadlocking. The code inside the lock could then resume on another thread (since when you yield, it doesn’t have to resume on the same thread), so you would be unlocking from a different thread than the one on which you established the lock.

See Also

The “Iterators,” “yield,” “IEnumerator Interface,” “IEnumerable(T) Interface,” and “IEnumerable Interface” topics in the MSDN documentation, and Recipe 9.9.

2.8 Dealing with finally Blocks and Iterators

Problem

You have added a try-finally block to your iterator, and you notice that the finally block is not being executed when you think it should.

Solution

Wrap a try block around the iteration code in the GetEnumerator iterator with a finally block following this try block:

public class StringSet : IEnumerable<string>
{
    private List<string> _items = new List<string>();

    public void Add(string value)
    {
        _items.Add(value);
    }

    public IEnumerator<string> GetEnumerator()
    {
        try
        {
            for (int index = 0; index < _items.Count; index++)
            {
                yield return (_items[index]);
            }
        }
        // Cannot use catch blocks in an iterator
        finally
        {
            // Only executed at end of foreach loop (including on yield break)
            Console.WriteLine("In iterator finally block");
        }
    }

    #region IEnumerable Members

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

    #endregion
}

The foreach code that calls this iterator looks like this:

//Create a StringSet object and fill it with data
StringSet strSet =
    new StringSet()
        {"item1",
            "item2",
            "item3",
            "item4",
            "item5"};

// Use the GetEnumerator iterator.
foreach (string s in strSet)
    Console.WriteLine(s);

When this code is run, the following output is displayed:

item1
item2
item3
item4
item5
In iterator finally block

Discussion

You may have thought that the output would display the "In iterator finally block" string after displaying each item in the strSet object. However, this is not the way that finally blocks are handled in iterators. All finally blocks associated with try blocks that have yield returns inside the iterator member body are called only after the iterations are complete, code execution leaves the foreach loop (such as when a break, return, or throw statement is encountered), or when a yield break statement is executed, effectively terminating the iterator.

To see how iterators deal with catch and finally blocks (note that there can be no catch blocks inside of a try block that contains a yield), consider the following code:

//Create a StringSet object and fill it with data
StringSet strSet =
    new StringSet()
        {"item1",
            "item2",
            "item3",
            "item4",
            "item5"};

// Display all data in StringSet object
try
{
    foreach (string s in strSet)
    {
        try
        {
            Console.WriteLine(s);
            // Force an exception here
            //throw new Exception();
        }
        catch (Exception)
        {
            Console.WriteLine("In foreach catch block");
        }
        finally
        {
            // Executed on each iteration
            Console.WriteLine("In foreach finally block");
        }
    }
}
catch (Exception)
{
    Console.WriteLine("In outer catch block");
}
finally
{
    // Executed on each iteration
    Console.WriteLine("In outer finally block");
}

Assuming that your original StringSet.GetEnumerator method is used (i.e., the one that contained the try-finally block), you will see the following behaviors.

If no exception occurs, you see this:

item1
In foreach finally block
item2
In foreach finally block
item3
In foreach finally block
item4
In foreach finally block
item5
In foreach finally block
In iterator finally block
In outer finally block

We see that the finally block that is within the foreach loop is executed on each iteration. However, the finally block within the iterator is executed only after all iterations are finished. Also, notice that the iterator’s finally block is executed before the finally block that wraps the foreach loop.

If an exception occurs in the iterator itself, during processing of the second element, the following is displayed:

item1
In foreach finally block
     (Exception occurs here...)
In iterator finally block
In outer catch block
In outer finally block

Notice that immediately after the exception is thrown, the finally block within the iterator is executed. This can be useful if you need to clean up only after an exception occurs. If no exception happens, then the finally block is not executed until the iterator completes. After the iterator’s finally block executes, the exception is caught by the catch block outside the foreach loop. At this point, the exception could be handled or rethrown. Once this catch block is finished processing, the outer finally block is executed.

Notice that the catch block within the foreach loop was never given the opportunity to handle the exception. This is because the corresponding try block does not contain a call to the iterator.

If an exception occurs in the foreach loop during processing of the second element, the following is displayed:

item1
In foreach finally block
     (Exception occurs here...)
In foreach catch block
In foreach finally block
In iterator finally block
In outer finally block

Notice in this situation that the catch and finally blocks within the foreach loop are executed first, then the iterator’s finally block. Lastly, the outer finally block is executed.

Understanding the way catch and finally blocks operate inside iterators will help you add them in the correct location. If you need a finally block to execute once immediately after the iterations are finished, add this finally block to the iterator method. If, however, you want the finally block to execute on each iteration, you need to place it within the foreach loop body.

If you need to catch iterator exceptions immediately after they occur, consider wrapping the foreach loop in a try-catch block. Any try-catch block within the foreach loop body will miss exceptions thrown from the iterator.

See Also

The “try-catch,” “Iterators,” “yield,” “IEnumerator Interface,” and “IEnumerable Interface” topics in the MSDN documentation.

2.9 Implementing Nested foreach Functionality in a Class

Problem

You need a class that contains a list of objects, with each of these objects itself containing a list of objects. You want to use a nested foreach loop to iterate through all objects in both the outer and inner lists in the following manner:

foreach (Group<Item> subGroup in topLevelGroup)
{
    // do work for groups
    foreach (Item item in subGroup)
    {
        // do work for items
    }
}

Solution

Implement the IEnumerable<T> interface on the class. The Group class shown in Example 2-5 contains a List<T> that can hold Group objects, and each Group object contains a List<Item>.

Example 2-5. Implementing foreach functionality in a class
public class Group<T> : IEnumerable<T>
{
    public Group(string name)
    {
        this.Name = name;
    }

    private List<T> _groupList = new List<T>();

    public string Name { get; set; }

    public int Count => _groupList.Count;

    public void Add(T group)
    {
        _groupList.Add(group);
    }

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

    public IEnumerator<T> GetEnumerator() => _groupList.GetEnumerator();
}


public class Item
{
    public Item(string name, int location)
    {
        this.Name = name;
        this.Location = location;
    }
    public string Name { get; set; }
    public int Location { get; set; }
}

Discussion

Building functionality into a class to allow it to be iterated over using the foreach loop is much easier using iterators in the C# language. In versions of the .NET Framework prior to 3.0, you not only had to implement the IEnumerable interface on the type that you wanted to make enumerable, but you also had to implement the IEnumerator interface on a nested class. You then had to write the MoveNext and Reset methods and the Current property by hand in this nested class. Iterators allow you to hand off the work of writing this nested class to the C# compiler. If you wrote an old-style enumerator yourself, it would look like this:

public class GroupEnumerator<T> : IEnumerator
{
    public T[] _items;

    int position = -1;

    public GroupEnumerator(T[] list)
    {
        _items = list;
    }

    public bool MoveNext()
    {
        position++;
        return (position < _items.Length);
    }

    public void Reset()
    {
        position = -1;
    }

    public object Current
    {
        get
        {
            try
            {
                return _items[position];
            }
            catch (IndexOutOfRangeException)
            {
                throw new InvalidOperationException();
            }
        }
    }
}

The IEnumerator.GetEnumerator method would be modified on the Group<T> class to look like this:

IEnumerator IEnumerable.GetEnumerator() =>
    new GroupEnumerator<T>(_groupList.ToArray());

and the code to walk over it would look like this:

IEnumerator enumerator = ((IEnumerable)hierarchy).GetEnumerator();
while (enumerator.MoveNext())
{
    Console.WriteLine(((Group<Item>)enumerator.Current).Name);
    foreach (Item i in ((Group<Item>)enumerator.Current))
    {
        Console.WriteLine(i.Name);
    }
}

Aren’t you glad you don’t have to do that? Leave it to the compiler; it’s quite good at writing this for you.

Enabling a class to be used by the foreach loop requires the inclusion of an iterator. An iterator can be a method, an operator overload, or the get accessor of a property that returns either a System.Collections.IEnumerator, a System.Collections.Generic.IEnumerator<T>, a System.Collections.IEnumerable, or a System.Collections.Generic.IEnumerable<T> and that contains at least one yield statement.

The code for this recipe is divided between two classes. The container class is the Group class, which contains a List of Group<Item> objects. The Group object also contains a List, but this List contains Item objects. To enumerate the contained list, the Group class implements the IEnumerable interface. It therefore contains a GetEnumerator iterator method, which returns an IEnumerator. The class structure looks like this:

Group (Implements IEnumerable<T>)
  Group (Implements IEnumerable<T>)
     Item

By examining the Group class, you can see how classes usable by a foreach loop are constructed. This class contains:

  • A simple List<T>, which will be iterated over by the class’s enumerator.

  • A property, Count, which returns the number of elements in the List<T>.

  • An iterator method, GetEnumerator, which is defined by the IEnumerable<T> interface. This method yields a specific value on each iteration of the foreach loop.

  • A method, Add, which adds an instance such as Subgroup to the List<T>.

  • A method, GetGroup, which returns a typed instance such as Subgroup from the List<T>.

To create the Subgroup class, you follow the same pattern as with the Group class—except the Subgroup class contains a List<Item>.

The final class is Item. This class is the lowest level of this structure and contains data. It has been grouped within the Subgroup objects, all of which are contained in the Group object. There is nothing out of the ordinary with this class; it simply contains data and the means to set and retrieve this data.

Using these classes is quite simple. The following method shows how to create a Group object that contains multiple Subgroup objects, which in turn contain multiple Item objects:

public static void CreateNestedObjects()
{
    Group<Group<Item>> hierarchy =
        new Group<Group<Item>>("root") {
            new Group<Item>("subgroup1"){
                new Item("item1",100),
                new Item("item2",200)},
            new Group<Item>("subgroup2"){
                new Item("item3",300),
                new Item("item4",400)}};

    IEnumerator enumerator = ((IEnumerable)hierarchy).GetEnumerator();
    while (enumerator.MoveNext())
    {
        Console.WriteLine(((Group<Item>)enumerator.Current).Name);
        foreach (Item i in ((Group<Item>)enumerator.Current))
        {
            Console.WriteLine(i.Name);
        }
    }

    // Read back the data
    DisplayNestedObjects(hierarchy);
}

The CreateNestedObjects method first creates a hierarchy object of the Group class and then creates two subgroups within it named subgroup1 and subgroup2. Each of these subgroup objects, in turn, is filled with two Item objects called item1, item2, item3, and item4.

The next method shows how to read all of the Item objects contained within the Group object that was created in the CreateNestedObjects method:

private static void DisplayNestedObjects(Group<Group<Item>> topLevelGroup)
{
    Console.WriteLine($"topLevelGroup.Count: {topLevelGroup.Count}");
    Console.WriteLine($"topLevelGroupName:  {topLevelGroup.Name}");

    // Outer foreach to iterate over all objects in the
    // topLevelGroup object
    foreach (Group<Item> subGroup in topLevelGroup)
    {
        Console.WriteLine($"	subGroup.SubGroupName:  {subGroup.Name}");
        Console.WriteLine($"	subGroup.Count: {subGroup.Count}");

        // Inner foreach to iterate over all Item objects in the
        // current SubGroup object
        foreach (Item item in subGroup)
        {
            Console.WriteLine($"		item.Name:     {item.Name}");
            Console.WriteLine($"		item.Location: {item.Location}");
        }
    }
}

This method displays the following:

topLevelGroup.Count: 2
topLevelGroupName:  root
        subGroup.SubGroupName:  subgroup1
        subGroup.Count: 2
                item.Name:     item1
                item.Location: 100
                item.Name:     item2
                item.Location: 200
        subGroup.SubGroupName:  subgroup2
        subGroup.Count: 2
                item.Name:     item3
                item.Location: 300
                item.Name:     item4
                item.Location: 400

As you see here, the outer foreach loop is used to iterate over all Subgroup objects that are stored in the top-level Group object. The inner foreach loop is used to iterate over all Item objects that are stored in the current Subgroup object.

See Also

The “Iterators,” “yield,” “IEnumerator Interface,” “IEnumerable(T) interface,” and “IEnumerable Interface” topics in the MSDN documentation.

2.10 Using a Thread-Safe Dictionary for Concurrent Access Without Manual Locking

Problem

You need to make a collection of key/value pairs accessible from multiple threads for reading and writing concurrently without having to manually use synchronization primitives to protect it.

Solution

Use the ConcurrentDictionary<TKey, TValue> to contain the items and access them in a thread-safe manner.

As an example, consider a simulation of fans entering a stadium or field to watch their favorite sporting event (Super Bowl, World Cup, World Series, or the ICC World Cricket League Championship) and there are only a certain number of gates.

First, we will need some Fans and we will keep track of their Names, when they are Admitted, and what AdmittanceGateNumber they enter by:

public class Fan
{
    public string Name { get; set; }
    public DateTime Admitted { get; set; }
    public int AdmittanceGateNumber { get; set; }
}

// set up a list of fans attending the event
List<Fan> fansAttending = new List<Fan>();
for (int i = 0; i < 100; i++)
    fansAttending.Add(new Fan() { Name = "Fan" + i });
Fan[] fans = fansAttending.ToArray();

Each gate can admit only one person at a time and at crowded events such as these, the gates are usually monitored by cameras. We will use a static ConcurrentDictionary<int,Fan> to represent the stadium gates as well as which Fan is currently at the gate, and a static bool (monitorGates) to indicate when the gates should no longer be monitored (i.e., when all fans have entered the event):

private static ConcurrentDictionary<int, Fan> stadiumGates =
    new ConcurrentDictionary<int, Fan>();

private static bool monitorGates = true;

We will say there are 10 gates (gateCount) to the event and launch a Task for each gate with the AdmitFans method to have each gate admit a certain number of fans. We will also launch a corresponding Task for each gate to have the security monitors turned on with the MonitorGate method. Once all of the fans have been admitted, we will stop monitoring the gates:

int gateCount = 10;
Task[] entryGates = new Task[gateCount];
Task[] securityMonitors = new Task[gateCount];

for (int gateNumber = 0; gateNumber < gateCount; gateNumber++)
{
    //FUN FACT:
    //You might think that as gateNumber changes in the for loop that the 
    //creation of the Task admitting the Fan would capture the value (0,1,2,etc.) 
    //at the point in time that the Task was created. As it turns out, the Task 
    //will use the CURRENT value in the gateNumber variable when it runs.
    //This means that even though you launched a Task for gate 0, it might get a
    //gateNumber variable with 9 in it as the loop has progressed since the Task 
    //was created.
    //To deal with this, we assign the values to a local variable which fixes the
    //scope and the value you wanted can be captured by the Task appropriately.
    int GateNum = gateNumber;
    int GateCount = gateCount;
    Action action = delegate () { AdmitFans(fans, GateNum, GateCount); };
    entryGates[gateNumber] = Task.Run(action);
}

for (int gateNumber = 0; gateNumber < gateCount; gateNumber++)
{
    int GateNum = gateNumber;
    Action action = delegate () { MonitorGate(GateNum); };
    securityMonitors[gateNumber] = Task.Run(action);
}

await Task.WhenAll(entryGates);

// Shut down monitoring
monitorGates = false;

AdmitFans does the work of admitting a section of the fans by using the ConcurrentDictionary<TKey, TValue>.AddOrUpdate method to show that the Fan is in the gate and then uses the ConcurrentDictionary<TKey, TValue>.TryRemove method to show that the Fan was admitted to the event:

private static void AdmitFans(Fan[] fans, int gateNumber, int gateCount)
{
    Random rnd = new Random();
    int fansPerGate = fans.Length / gateCount;
    int start = gateNumber * fansPerGate;
    int end = start + fansPerGate - 1;
    for (int f = start; f <= end; f++)
    {
        Console.WriteLine($"Admitting {fans[f].Name} through gate {gateNumber}");
        var fanAtGate =
            stadiumGates.AddOrUpdate(gateNumber, fans[f],
                (key, fanInGate) =>
                {
                    Console.WriteLine($"{fanInGate.Name} was replaced by " +
                        $"{fans[f].Name} in gate {gateNumber}");
                    return fans[f];
                });
        // Perform patdown check and check ticket
        Thread.Sleep(rnd.Next(500, 2000));
        // Let them through the gate
        fans[f].Admitted = DateTime.Now;
        fans[f].AdmittanceGateNumber = gateNumber;
        Fan fanAdmitted;
        if(stadiumGates.TryRemove(gateNumber, out fanAdmitted))
            Console.WriteLine($"{fanAdmitted.Name} entering event from gate " +
                $"{fanAdmitted.AdmittanceGateNumber} on " +
                $"{fanAdmitted.Admitted.ToShortTimeString()}");
        else // if we couldn't admit them, security must have gotten them...
            Console.WriteLine($"{fanAdmitted.Name} held by security " +
                $"at gate {fanAdmitted.AdmittanceGateNumber}");
    }
}

MonitorGate watches the provided gate number (gateNumber) by inspecting the ConcurrentDictionary<TKey, TValue> using the TryGetValue method. MonitorGate will continue to monitor until the monitorGates flag is set to false (after the AdmitFan Tasks have completed):

private static void MonitorGate(int gateNumber)
{
    Random rnd = new Random();
    while (monitorGates)
    {
        Fan currentFanInGate;
        if (stadiumGates.TryGetValue(gateNumber, out currentFanInGate))
            Console.WriteLine($"Monitor: {currentFanInGate.Name} is in Gate " +
                $"{gateNumber}");
        else
            Console.WriteLine($"Monitor: No fan is in Gate {gateNumber}");

        // Wait and then check gate again
        Thread.Sleep(rnd.Next(500, 5000));
    }
}

Discussion

ConcurrentDictionary<TKey, TValue> is located in the System.Collections.Concurrent namespace and is available from .NET 4.0 and up. It is a collection that is most useful in multiple read and multiple write scenarios. If you are simply doing multiple reads after an initialization, ImmutableDictionary<TKey,TValue> would be a better choice. Since the dictionary is immutable once loaded, it is a much faster readable collection. When you manipulate an immutable dictionary a whole new copy of the original dictionary is made, which can be very expensive, so keep this in your back pocket for collections you load up once and read from a lot.

Note that the Immutable classes are not part of the core .NET Framework libraries; they are in the System.Collections.Immutable assembly in the Microsoft.Bcl.Immutable NuGet package, which you would need to include in your application.

There are a number of properties and methods on ConcurrentDictionary<TKey, TValue> that cause the entire collection to lock, as they are operating on all of the items in the collection at once. These properties give you a “point in time” snapshot of the data:

  • Count property

  • Keys property

  • Values property

  • ToArray method

A “point in time” representation of the data means that you’re not necessarily getting the current data while enumerating. If you need the absolutely current data from the dictionary, use the GetEnumerator method, which provides an enumerator that rolls over the key/value pairs in the dictionary. Since GetEnumerator guarantees that the enumerator is safe for use even in the face of concurrent updates and it doesn’t take any locks, you can access the underlying data while it is being changed. Note that this means that your results could change during the enumeration. See the “IEnumerable<T>.GetEnumerator” topic in MSDN for a more detailed description.

LINQ uses GetEnumerator frequently to accomplish its goals, so you can avoid the lock penalty of using the Keys and Values properties by instead using a LINQ Select to get the items:

var keys = stadiumGates.Select(gate => gate.Key);
var values = stadiumGates.Select(gate => gate.Value);

For counting, LINQ is optimized to use the Count property on collections that support the ICollection interface, which is not what we want in this case. This example demonstrates the logic of the Count method; the Count property is used if the enumerable supports ICollection or ICollection<T>:

public static int Count<TSource>(this IEnumerable<TSource> source)
 {
     if (source == null)
     {
         throw Error.ArgumentNull("source");
     }
     ICollection<TSource> tSources = source as ICollection<TSource>;
     if (tSources != null)
     {
         return tSources.Count;
     }
     ICollection collections = source as ICollection;
     if (collections != null)
     {
         return collections.Count;
     }
     int num = 0;
     using (IEnumerator<TSource> enumerator = source.GetEnumerator())
     {
         while (enumerator.MoveNext())
         {
             num++;
         }
     }
     return num;
 }

To work around this, get an enumerable collection of the items that doesn’t support ICollection or ICollection<T> and call Count() on it:

var count = stadiumGates.Select(gate => gate).Count();

The call to Select returns a System.Linq.Enumerable.WhereSelectEnumerableIterator that does not support ICollection or ICollection<T> but does support GetEnumerator.

The main manipulation operations on ConcurrentDictionary<TKey, TValue> are summed up in Table 2-1.

Table 2-1. ConcurrentDictionary manipulation operations
Method When to use
TryAdd Add new item only if key doesn’t exist.
TryUpdate Update an existing key with a new value if the current value is available.
Indexing Set a key/value in the dictionary unconditionally whether the key exists or not.
AddOrUpdate Set a key/value in the dictionary with delegates to allow different entries if the key is being added or updated.
GetOrAdd Get the value for a key or default the value and return it (lazy initialization).
TryGetValue Get the value for the key or return false.
TryRemove Remove the value for the key or return false.

The beginning output from the example code will look similar to this:

Admitting Fan0 through gate 0
Admitting Fan10 through gate 1
Admitting Fan20 through gate 2
Admitting Fan30 through gate 3
Admitting Fan40 through gate 4
Fan0 entering event from gate 0 on 6:00 PM
Admitting Fan1 through gate 0
Fan20 entering event from gate 2 on 6:00 PM
Fan10 entering event from gate 1 on 6:00 PM
Admitting Fan11 through gate 1
Fan30 entering event from gate 3 on 6:00 PM
Admitting Fan31 through gate 3
Admitting Fan21 through gate 2
Fan40 entering event from gate 4 on 6:00 PM
Admitting Fan41 through gate 4
Admitting Fan50 through gate 5
Fan11 entering event from gate 1 on 6:00 PM
Admitting Fan12 through gate 1
Fan1 entering event from gate 0 on 6:00 PM

The output from the monitors in the middle of the example code run will look similar to this:

...
Admitting Fan17 through gate 1
Fan6 entering event from gate 0 on 6:00 PM
Admitting Fan7 through gate 0
Fan26 entering event from gate 2 on 6:00 PM
Admitting Fan27 through gate 2
Fan36 entering event from gate 3 on 6:00 PM
Admitting Fan37 through gate 3
Monitor: Fan17 is in Gate 1
Fan83 entering event from gate 8 on 6:00 PM
Admitting Fan55 through gate 5
Fan17 entering event from gate 1 on 6:00 PM
Admitting Fan18 through gate 1
...

The output at the end of the example code will look like this:

...
Monitor: Fan97 is in Gate 9
Monitor: No fan is in Gate 0
Fan77 entering event from gate 7 on 6:00 PM
Admitting Fan78 through gate 7
Monitor: No fan is in Gate 1
Fan97 entering event from gate 9 on 6:00 PM
Admitting Fan98 through gate 9
Monitor: No fan is in Gate 2
Monitor: No fan is in Gate 3
Monitor: No fan is in Gate 4
Monitor: No fan is in Gate 5
Monitor: No fan is in Gate 6
Monitor: Fan78 is in Gate 7
Monitor: No fan is in Gate 8
Fan78 entering event from gate 7 on 6:00 PM
Admitting Fan79 through gate 7
Monitor: Fan98 is in Gate 9
Monitor: No fan is in Gate 2
Fan98 entering event from gate 9 on 6:00 PM
Admitting Fan99 through gate 9
Monitor: No fan is in Gate 1
Monitor: No fan is in Gate 2
Fan79 entering event from gate 7 on 6:00 PM
Fan99 entering event from gate 9 on 6:00 PM

See Also

The “IEnumerable<T>.GetEnumerator” and “ConcurrentDictionary<TKey, TValue>” topics in the MSDN documentation.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.17.154.16